使用具有不同JSON结构的Gson解析Retrofit2结果 [英] Parsing Retrofit2 result using Gson with different JSON structures

查看:847
本文介绍了使用具有不同JSON结构的Gson解析Retrofit2结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我调用API时,根据参数,JSON中相同字段的名称返回更改。在下面的例子中,在一种情况下,user字段被命名为userA或userB。
我使用的是Gson,但我不想创建一个对象来解析JSON的根,因为我只对用户列表感兴趣,而且我希望我的调用只返回这个列表。

When I call the API, depending on the parameters, the name of the same field in the JSON returned changes. In the example below, in one case the field "user" is named "userA" or "userB". I'm using Gson but I don't want to create an object to parse the root of the JSON as I'm only interested in the list of users and I want my call to return this list only.

{
    "apiVersion":"42"
    "usersA":[{
        "name":"Foo",
        "lastname":"Bar"
        ...
    }
    ]
    "otherData":"..."
}

{
    "apiVersion":"42"
    "usersB":[{
        "name":"Foo",
        "lastname":"Bar"
        ...
    }
    ]
    "otherData":"..."
}

我知道我可以使用TypeAdapter,但我想使用相同的Retrofit客户端来执行不同的调用,并且根据API终点,JSON结构可能会非常不同。

I know that I could use a TypeAdapter but I want to use the same Retrofit client to do different calls and the JSON structure can be very different depending on the API end point.

我该怎么做?

推荐答案


我知道我可以使用TypeAdapter,但我想使用相同的Retrofit客户端做不同的调用,根据API的终点,JSON结构可能会有很大的不同。

I know that I could use a TypeAdapter but I want to use the same Retrofit client to do different calls and the JSON structure can be very different depending on the API end point.

好的,你可以做到这一点,使用Retrofit 2而不是普通的Gson更容易。

Well, you can do it, and it's even easier with Retrofit 2 rather than plain Gson.

例如

For example

final class User {

    final String name = null;
    final String lastname = null;

}



interface IService {

    @GET("/")
    @Unwrap
    Call<List<User>> getUsers();

}

请注意 @Unwrap 注释。这是一个可选的自定义注释,标注调用响应主体应该是unwrapped:
$ b

Note the @Unwrap annotation above. This is an optional custom annotation marking that the call response body should be "unwrapped":

@Retention(RUNTIME)
@Target(METHOD)
@interface Unwrap {
}

现在您可以创建一个Retrofit转换工厂来分析注释。当然,这不能涵盖所有情况,但它是可扩展的,您可以改进它:
$ b

Now you can just create a Retrofit converter factory that would analyze the annotation. Of course, this cannot cover all the cases, but it's extensible and you can improve it:

final class UnwrappingGsonConverterFactory
        extends Converter.Factory {

    private final Gson gson;

    private UnwrappingGsonConverterFactory(final Gson gson) {
        this.gson = gson;
    }

    static Converter.Factory create(final Gson gson) {
        return new UnwrappingGsonConverterFactory(gson);
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {
        if ( !needsUnwrapping(annotations) ) {
            return super.responseBodyConverter(type, annotations, retrofit);
        }
        final TypeAdapter<?> typeAdapter = gson.getAdapter(TypeToken.get(type));
        return new UnwrappingResponseConverter(typeAdapter);
    }

    private static boolean needsUnwrapping(final Annotation[] annotations) {
        for ( final Annotation annotation : annotations ) {
            if ( annotation instanceof Unwrap ) {
                return true;
            }
        }
        return false;
    }

    private static final class UnwrappingResponseConverter
            implements Converter<ResponseBody, Object> {

        private final TypeAdapter<?> typeAdapter;

        private UnwrappingResponseConverter(final TypeAdapter<?> typeAdapter) {
            this.typeAdapter = typeAdapter;
        }

        @Override
        public Object convert(final ResponseBody responseBody)
                throws IOException {
            try ( final JsonReader jsonReader = new JsonReader(responseBody.charStream()) ) {
                // Checking if the JSON document current value is null
                final JsonToken token = jsonReader.peek();
                if ( token == JsonToken.NULL ) {
                    return null;
                }
                // If it's an object, expect `{`
                jsonReader.beginObject();
                Object value = null;
                // And iterate over all properties
                while ( jsonReader.hasNext() ) {
                    final String name = jsonReader.nextName();
                    // I'm assuming apiVersion and otherData should be skipped
                    switch ( name ) {
                    case "apiVersion":
                    case "otherData":
                        jsonReader.skipValue();
                        break;
                    // But any other is supposed to contain the required value (or null)
                    default:
                        value = typeAdapter.read(jsonReader);
                        break;
                    }
                }
                // Consume the object end `}`
                jsonReader.endObject();
                return value;
            } finally {
                responseBody.close();
            }
        }

    }

}

我用下面的代码测试了它:
$ b

I've tested it with the following code:

for ( final String filename : ImmutableList.of("usersA.json", "usersB.json") ) {
    // Mocking the HTTP client to return a JSON document always
    final OkHttpClient client = new Builder()
            .addInterceptor(staticResponse(Q43921751.class, filename))
            .build();
    // Note the order of converter factories
    final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://whatever")
            .client(client)
            .addConverterFactory(UnwrappingGsonConverterFactory.create(gson))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build();
    final IService service = retrofit.create(IService.class);
    service.getUsers()
            .execute()
            .body()
            .forEach(user -> System.out.println(user.name + " " + user.lastname));
}

输出:

Output:


Foo Bar

Foo Bar

Foo Bar
Foo Bar

这篇关于使用具有不同JSON结构的Gson解析Retrofit2结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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