java - 将 JSON 对象反序列化为对象数组以进行 Retrofit

标签 java android json gson retrofit

我在使用外部 API 的 Android 应用程序中使用 Retrofit。问题在于我无法弄清楚如何反序列化以返回对象列表的响应。我得到的 JSON 格式如下:

{
    "attribute_1": "value",
    "attribute_2": "value",
    "member_1": {
        "param1": "value1",
        "param2": "value2"
    },
    "member_2": {
        "param1": "value1",
        "param2": "value2"
    },
    ...
}

Retrofit 中的 API 调用如下所示:

@GET("apiednpoint/path")
Call<List<Member>> getMembers();

我想忽略属性并检索 List<Member>从这个回应。我知道我可以创建一个自定义反序列化器来忽略 JSON 中的某些字段,例如 here并将成员转换为数组,如 here ,但在第二个链接中,我需要一个来 self 的 List<Member> 的包装类我所期望的。是否可以不用包装器来完成我所期望的列表/数组?

最佳答案

它不是免费的,但可以通过 Retrofit 实现(对于独立的 Gson 来说并不容易,因为它需要更多的“魔法”)。以下解决方案远非完美,但您可以根据需要进行改进。假设您有以下 Member 映射:

final class Member {

    final String param1 = null;
    final String param2 = null;

}

以及以下服务:

interface IService {

    @GET("/")
    @ByRegExp("member_.+")
    Call<List<Member>> getMembers();

}

请注意,@ByRegExp 是一个自定义注释,将在下面进行处理。注解声明:

@Retention(RUNTIME)
@Target(METHOD)
@interface ByRegExp {

    String value();

}

现在,以下代码借用了我的调试/模拟代码中的一些代码,但它可以轻松转换为您的真实代码:

// This is just a mocked HTTP client that always returns your members.json
final OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(staticResponse(Q43925012.class, "members.json"))
        .build();
// Gson stuff
final Gson gson = new GsonBuilder()
        // ... configure your Gson here ...
        .create();
final Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://whatever")
        .client(client)
        .addConverterFactory(new Converter.Factory() {
            @Override
            public Converter<ResponseBody, ?> responseBodyConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {
                // Checking if the method is declared with @ByRegExp annotation
                final ByRegExp byRegExp = findByRegExp(annotations);
                if ( byRegExp != null ) {
                    // If so, then compile the regexp pattern
                    final Pattern pattern = Pattern.compile(byRegExp.value());
                    // And resolve the list element type
                    final Type listElementType = getTypeParameter0(type);
                    // Obtaining the original your-type list type adapter
                    final TypeAdapter<?> listElementTypeAdapter = gson.getAdapter(TypeToken.get(listElementType));
                    return (Converter<ResponseBody, Object>) responseBody -> {
                        try {
                            // Getting input stream from the response body and converting it to a JsonReader -- a low level JSON parser
                            final JsonReader jsonReader = new JsonReader(new InputStreamReader(responseBody.byteStream()));
                            final List<Object> list = new ArrayList<>();
                            // Make sure that the first token is `{`
                            jsonReader.beginObject();
                            // And iterate over each JSON property
                            while ( jsonReader.hasNext() ) {
                                final String name = jsonReader.nextName();
                                final Matcher matcher = pattern.matcher(name);
                                // Check if the property need matches the pattern
                                if ( matcher.matches() ) {
                                    // And if so, just deserialize it and put it to the result list
                                    final Object element = listElementTypeAdapter.read(jsonReader);
                                    list.add(element);
                                } else {
                                    // Or skip the value entirely
                                    jsonReader.skipValue();
                                }
                            }
                            // make sure that the current JSON token is `{` - NOT optional
                            jsonReader.endObject();
                            return list;
                        } finally {
                            responseBody.close();
                        }
                    };
                }
                return super.responseBodyConverter(type, annotations, retrofit);
            }

            private ByRegExp findByRegExp(final Annotation[] annotations) {
                for ( final Annotation annotation : annotations ) {
                    if ( annotation instanceof ByRegExp ) {
                        return (ByRegExp) annotation;
                    }
                }
                return null;
            }

            // Trying to resolve how List<E> is parameterized (or raw if not)
            private Type getTypeParameter0(final Type type) {
                if ( !(type instanceof ParameterizedType) ) {
                    return Object.class;
                }
                final ParameterizedType parameterizedType = (ParameterizedType) type;
                return parameterizedType.getActualTypeArguments()[0];
            }
        })
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();
final IService service = retrofit.create(IService.class);
final List<Member> members = service.getMembers()
        .execute()
        .body();
for ( final Member member : members ) {
    System.out.println(member.param1 + ", " + member.param2);
}

输出:

value1, value2
value1, value2

我认为它不会更容易实现(就 Gson/Retrofit 交互而言),但我希望它有所帮助。

关于java - 将 JSON 对象反序列化为对象数组以进行 Retrofit,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43925012/

相关文章:

android - 构建 Python 3.7 时 bz2 模块失败

c - 使用 C 在 Berkeley DB 中存储 JSON 字符串

python - 通过 python 在 Azure DevOps 中创建工作项

java - 如何将服务器设置为 "deploy"以便可以通过 Internet 访问它?

java - 我需要有关 java.lang.IndexOutOfBoundsException - ArrayList 的帮助

RTL 语言的 Android 本地化

javascript - 设备架构中调用的遥测中的数组字段类型是什么?

java - 为什么 JPanel 会自动重绘?

java - iText 不添加一行表格

android - 文本 block 和图像的定位