Retrofit和Gson:解析数组/元素多态对象 [英] Retrofit and Gson: parsing array/element-polymorphic objects

查看:717
本文介绍了Retrofit和Gson:解析数组/元素多态对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

 parameters:{
parameter:{
Data:value
}
},

参数:{
参数:[
{
Data:value
},
{
Data:value
},
]
},

如果我调用 List< Class> 参数:


预期的BEGIN_OBJECT,但获取BEGIN_ARRAY


我需要解析参数来获取值

  public class ApiClient {
public static final String BASE_URL =http:// .........;

private static Retrofit retrofit = null;
$ b public static Retrofit getClient(){
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(1,TimeUnit.MINUTES)
.writeTimeout(1, TimeUnit.MINUTES)
.readTimeout(1,TimeUnit.MINUTES)
.addInterceptor(new ServiceGenerator(Content-Type,application / json))。build();

Gson gson = new GsonBuilder()
.setLenient()
.create();
if(retrofit == null){
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
。客户端(客户端)
.build();
}
返回改造;
}

}

  public class ServiceGenerator实现Interceptor {
private String httpUsername;
私人字符串httpPassword;

public ServiceGenerator(String httpUsername,String httpPassword){
this.httpUsername = httpUsername;
this.httpPassword = httpPassword;

$ b @Override
public response拦截(链式链接)抛出IOException {
Request newRequest = chain.request()。newBuilder()

.addHeader(Authorization,getAuthorizationValue())

.build();

return chain.proceed(newRequest);
}

private String getAuthorizationValue(){
final String userAndPassword = httpUsername +:+ httpPassword;
返回Basic+ Base64.encodeToString(userAndPassword.getBytes(),Base64.NO_WRAP);
}

}

  @POST(OneWay.json)
呼叫< ApiResponse> sendOneWay(@Body查询数据);

@SerializedName(FlightDetails)
public ApiResponse FlightDetails;

现在我调用一个类ApiResponse
但是如何调用
public ApiResponse航班详情; &安培; public List FlightDetails;

解决方案

这只是一个非常微不足道的问题,它经常发生在具有奇怪设计选择的API上。您只需将两种格式对齐为统一格式即可:列表可以涵盖两种情况。所以,你必须实现的是一个类型适配器,它会检查这种对齐是否必要,如果该值是一个列表,则使用原始类型适配器,或者将其包含在单个元素列表中。



为简单起见,请考虑以下JSON文档:

single.json



<$
virtual:{
key-1:value-1
}
}
/ code>



multiple.json



  {
virtual:[
{
key-1:value-1
},
{
key-2 :value-2
}
]
}

现在使用对齐的字段定义一个映射:
$ b

  final class Response {

@JsonAdapter(AlwaysListTypeAdapterFactory.class)
final List< Map< String,String>> virtual = null;

$ b

请注意 JsonAnnotaion 注释:这是一种告诉Gson如何读取或写入字段的方法。 AlwaysListTypeAdapterFactory 实现可能如下:

  final class AlwaysListTypeAdapterFactory 
实现TypeAdapterFactory {

//总是考虑将构造函数设为private
// + Gson可以实例化这个工厂本身
private AlwaysListTypeAdapterFactory(){
}

@Override
public< T> TypeAdapter< T>创建(最终的Gson gson,最终的TypeToken< T> typeToken){
//不是列表?
if(!List.class.isAssignableFrom(typeToken.getRawType())){
//不是我们可以处理的
返回null;
}
//现在只需返回一个特殊类型的适配器,它可以检测如何处理对象
@SuppressWarnings(unchecked)
final TypeAdapter< T> (TypeAdapter< T>)new AlwaysListTypeAdapter<>(
(TypeAdapter< Object>)gson.getAdapter(TypeToken.get(getTypeParameter0(typeToken.getType()))),
(TypeAdapter< List< Object>>)gson.getAdapter(typeToken)
);
返回castTypeAdapter;


//用于检测列表参数化
private static Type getTypeParameter0(final Type type){
if(!(type of instanceof ParameterizedType)) {
//是通配符还是原始类型?然后我们不能确定真正的参数化
返回Object.class;
}
//或者只是在列表< E>中解析实际的E,
final ParameterizedType parameterizedType =(ParameterizedType)type;
返回parameterizedType.getActualTypeArguments()[0];
}

private static final class AlwaysListTypeAdapter< E>
扩展TypeAdapter< List< E>> {

private final TypeAdapter< E> elementTypeAdapter;
private final TypeAdapter< List< E>> listTypeAdapter;

private AlwaysListTypeAdapter(final TypeAdapter< E> elementTypeAdapter,final TypeAdapter< List< E>> listTypeAdapter){
this.elementTypeAdapter = elementTypeAdapter;
this.listTypeAdapter = listTypeAdapter;

$ b @Override
public void write(final JsonWriter out,final List< E> value)
throws IOException {
listTypeAdapter.write(out ,价值);
}

@Override
public List< E> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch(token){
case BEGIN_ARRAY:
//如果下一个标记是[,假设是一个普通列表,只是将作业委托给Gson内部
返回listTypeAdapter。读取(在);
case BEGIN_OBJECT:
case STRING:
case NUMBER:
case BOOLEAN:
case NULL:
//任何其他值?尽管Collections.singletonList()可能被使用,但Gson会返回可变的ArrayList实例,所以我们做...
final List< E> list = new ArrayList<>();
list.add(elementTypeAdapter.read(in));
返回列表;
案例EN​​D_ARRAY:
案例EN​​D_OBJECT:
案例名称:
案例EN​​D_DOCUMENT:
//这里可怕的事情...
抛出新的MalformedJsonException(Unexpected token:+ token +在+ in;
默认值:
//如果有一天Gson添加一个新的令牌
抛出新的AssertionError(token);
}
}

}

}

测试:
$ b

  public static void main(final String .. (最后一个字符串资源:ImmutableList.of(single.json,multiple.json)){
try(final Reader reader = getPackageResourceReader(args)
throws IOException {
Q43634110.class,resource)){
final Response response = gson.fromJson(reader,Response.class);
System.out.println(resource);
System.out.println(\t+ response.virtual);
}
}
}

输出:


single.json

[{key-1 = value-1}]

multiple.json <
[{key-1 = value-1},{key-2 = value-2}]



I am getting response in a sequence:

 "parameters": {
        "parameter": {
                     "Data":"value"
                     }
              },

 "parameters":{
        "parameter": [
                     {
                     "Data":"value"
                     },
                     {
                     "Data":"value"
                     },
                     ]
              },

Getting the error if I call List<Class> parameter:

Expected BEGIN_OBJECT but getting BEGIN_ARRAY

I need to parse parameter to get values

public class ApiClient  {
public static final String BASE_URL ="http://.........";

private static Retrofit retrofit = null;

public static Retrofit getClient() {
    OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(1, TimeUnit.MINUTES)
            .writeTimeout(1, TimeUnit.MINUTES)
            .readTimeout(1, TimeUnit.MINUTES)
            .addInterceptor(new ServiceGenerator("Content-Type","application/json")).build();

    Gson gson = new GsonBuilder()
            .setLenient()
            .create();
    if (retrofit==null) {
        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .client(client)
                .build();
    }
    return retrofit;
}

}

public class ServiceGenerator implements Interceptor{
private String httpUsername;
private String httpPassword;

public ServiceGenerator(String httpUsername, String httpPassword) {
    this.httpUsername = httpUsername;
    this.httpPassword = httpPassword;
}

@Override
public Response intercept(Chain chain) throws IOException {
    Request newRequest = chain.request().newBuilder()

            .addHeader("Authorization", getAuthorizationValue())

            .build();

    return chain.proceed(newRequest);
}

private String getAuthorizationValue() {
    final String userAndPassword = httpUsername + ":" + httpPassword;
    return "Basic " + Base64.encodeToString(userAndPassword.getBytes(), Base64.NO_WRAP);
}

}

@POST("OneWay.json")
Call<ApiResponse> sendOneWay(@Body Query data);

@SerializedName("FlightDetails")
public ApiResponse FlightDetails;

Now I called a Class ApiResponse But How to call both public ApiResponse FlightDetails; & public List FlightDetails;

解决方案

This is just a very trivial issue that occurs often with APIs that have weird design choices. You just have to "align" both formats to a unified form: lists can cover both cases. So, all you have to implement is a type adapter that would check if such an alignment is necessary and use either the original type adapter if the value is a list, or wrap it up in a single element list.

For simplicity, consider the following JSON documents:

single.json

{
    "virtual": {
        "key-1": "value-1"
    }
}

multiple.json

{
    "virtual": [
        {
            "key-1": "value-1"
        },
        {
            "key-2": "value-2"
        }
    ]
}

Now define a mapping with the aligned field:

final class Response {

    @JsonAdapter(AlwaysListTypeAdapterFactory.class)
    final List<Map<String, String>> virtual = null;

}

Note the JsonAnnotaion annotation: this is a way to tell Gson how the field must be read or written. The AlwaysListTypeAdapterFactory implementation might be as follows:

final class AlwaysListTypeAdapterFactory
        implements TypeAdapterFactory {

    // Always consider making constructors private
    // + Gson can instantiate this factory itself   
    private AlwaysListTypeAdapterFactory() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Not a list?
        if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
            // Not something we can to deal with
            return null;
        }
        // Now just return a special type adapter that could detect how to deal with objects
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) new AlwaysListTypeAdapter<>(
                (TypeAdapter<Object>) gson.getAdapter(TypeToken.get(getTypeParameter0(typeToken.getType()))),
                (TypeAdapter<List<Object>>) gson.getAdapter(typeToken)
        );
        return castTypeAdapter;
    }

    // This is used to detect the list parameterization
    private static Type getTypeParameter0(final Type type) {
        if ( !(type instanceof ParameterizedType) ) {
            // Is it a wildcard or raw type? Then we cannot determine the real parameterization
            return Object.class;
        }
        // Or just resolve the actual E in List<E>
        final ParameterizedType parameterizedType = (ParameterizedType) type;
        return parameterizedType.getActualTypeArguments()[0];
    }

    private static final class AlwaysListTypeAdapter<E>
            extends TypeAdapter<List<E>> {

        private final TypeAdapter<E> elementTypeAdapter;
        private final TypeAdapter<List<E>> listTypeAdapter;

        private AlwaysListTypeAdapter(final TypeAdapter<E> elementTypeAdapter, final TypeAdapter<List<E>> listTypeAdapter) {
            this.elementTypeAdapter = elementTypeAdapter;
            this.listTypeAdapter = listTypeAdapter;
        }

        @Override
        public void write(final JsonWriter out, final List<E> value)
                throws IOException {
            listTypeAdapter.write(out, value);
        }

        @Override
        public List<E> read(final JsonReader in)
                throws IOException {
            final JsonToken token = in.peek();
            switch ( token ) {
            case BEGIN_ARRAY:
                // If the next token is [, assume is a normal list, and just delegate the job to Gson internals
                return listTypeAdapter.read(in);
            case BEGIN_OBJECT:
            case STRING:
            case NUMBER:
            case BOOLEAN:
            case NULL:
                // Any other value? Wrap it up ourselves, but use the element type adapter
                // Despite Collections.singletonList() might be used, Gson returns mutable ArrayList instances, so we do...
                final List<E> list = new ArrayList<>();
                list.add(elementTypeAdapter.read(in));
                return list;
            case END_ARRAY:
            case END_OBJECT:
            case NAME:
            case END_DOCUMENT:
                // Something terrible here...
                throw new MalformedJsonException("Unexpected token: " + token + " at " + in);
            default:
                // If someday Gson adds a new token
                throw new AssertionError(token);
            }
        }

    }

}

The test:

public static void main(final String... args)
        throws IOException {
    for ( final String resource : ImmutableList.of("single.json", "multiple.json") ) {
        try ( final Reader reader = getPackageResourceReader(Q43634110.class, resource) ) {
            final Response response = gson.fromJson(reader, Response.class);
            System.out.println(resource);
            System.out.println("\t" + response.virtual);
        }
    }
}

Output:

single.json
[{key-1=value-1}]
multiple.json
[{key-1=value-1}, {key-2=value-2}]

这篇关于Retrofit和Gson:解析数组/元素多态对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆