android - 使用具有不同 JSON 结构的 Gson 解析 Retrofit2 结果

标签 android json gson retrofit2

当我调用 API 时,根据参数,返回的 JSON 中相同字段的名称会发生​​变化。在下面的示例中,在一种情况下,字段“user”被命名为“userA”或“userB”。 我正在使用 Gson,但我不想创建一个对象来解析 JSON 的根,因为我只对用户列表感兴趣并且我希望我的调用仅返回此列表。

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

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

我知道我可以使用 TypeAdapter,但我想使用同一个 Retrofit 客户端来执行不同的调用,并且 JSON 结构可能会因 API 端点的不同而大不相同。

我该怎么做?

最佳答案

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 更容易。

例如

final class User {

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

}
interface IService {

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

}

注意上面的@Unwrap 注释。这是一个可选的自定义注释,标记调用响应主体应该“展开”:

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

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

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();
            }
        }

    }

}

我已经用下面的代码测试了它:

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));
}

输出:

Foo Bar
Foo Bar

关于android - 使用具有不同 JSON 结构的 Gson 解析 Retrofit2 结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43921751/

相关文章:

android - Android 和 Safari 浏览器中的 Flex Box

android - 单个项目的内容提供商 uri 与项目列表的 uri

java - 当格式已知时从 String 解析 JSON 的最快方法

java - Gson:反序列化一个计算值

Android Internet连接检查更好的方法

带抽屉导航的工具栏上的 Android 'back' 操作

java - 如何将 JSONObject 转换为 JSONArray

mysql - 将json字段与数据库表字段映射

javascript - 使用 AJAX 解析 JSON

java - 改造 : How to decide the class members?