简体   繁体   English

将JSON对象反序列化为Retrofit对象数组

[英]Deserialize JSON object into array of objects for Retrofit

I'm using Retrofit in my Android app that utilizes external API. 我在使用外部API的Android应用中使用Retrofit。 The problem is in the response that I can't figure out how to deserialize to return list of objects. 问题出在响应中,我无法弄清楚如何反序列化以返回对象列表。 JSON I get has such format: 我得到的JSON有这样的格式:

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

API call in Retrofit looks like this: Retrofit中的API调用如下所示:

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

I want to ignore attributes and retrieve List<Member> from this response. 我想忽略属性并从此响应中检索List<Member> I know I can create a custom deserializer to ignore some fields within JSON like here and convert members into an array like here , but in the second link I'd need a wrapper class form my List<Member> that I expect. 我知道我可以创建一个自定义反序列化器来忽略JSON中的某些字段,就像这里一样,并将成员转换为类似这里的数组,但在第二个链接中,我需要一个包装类来形成我期望的List<Member> Is it possible to do without wrapper around my list / array that expect? 是否可以在我的列表/数组周围没有包装?

I Believe this small code will help you (Asumming that json is the JSON response that you described): 我相信这个小代码会帮助你(假设json是你描述的JSON响应):

public List<Member> deserialize(String json) {
    com.google.gson.Gson gson;
    com.google.gson.JsonParser.JsonParser parser;
    com.google.gson.JsonElement elem;
    com.google.gson.JsonObject jsonObj;
    java.util.List<Member> members;

    gson    = new Gson();
    parser  = new JsonParser();
    elem    = parser.parse(json);
    jsonObj = element.getAsJsonObject();
    members = new ArrayList<>();

    for (Map.Entry<String, JsonElement> entry : jsonObj.entrySet()) {
        if (entry.getKey().startsWith("member_")) {
            members.add( gson.fromJson(entry.getValue(), Member.class) );
        }
    }
    return members;
}

It's not free but it's possible with Retrofit (and not easy with standalone Gson because it would require more "magic"). 它不是免费的,但它可以使用Retrofit(而且对于独立的Gson来说并不容易,因为它需要更多的“魔法”)。 The following solution is far from perfect, but you can improve it according to your needs. 以下解决方案远非完美,但您可以根据自己的需要进行改进。 Let's say you have he following Member mapping: 假设你有他跟随Member映射:

final class Member {

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

}

And the following service: 以下服务:

interface IService {

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

}

Note that @ByRegExp is a custom annotation that will be processed below. 请注意@ByRegExp是一个自定义注释,将在下面处理。 The annotation declaration: 注释声明:

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

    String value();

}

Now, the following code borrows some code from my debug/mocking code, but it can be easily translated to your real code: 现在,以下代码从我的调试/模拟代码中借用了一些代码,但它可以很容易地转换为您的真实代码:

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

Output: 输出:

value1, value2 value1,value2
value1, value2 value1,value2

I don't think it can be easier to implement (in terms of Gson/Retrofit interaction), but I hope it helps. 我认为它不容易实现(在Gson / Retrofit交互方面),但我希望它有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM