【问题标题】:Deserialize JSON object into array of objects for Retrofit将 JSON 对象反序列化为对象数组以进行改造
【发布时间】:2017-05-11 20:31:47
【问题描述】:

我在使用外部 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&lt;Member&gt;。我知道我可以创建一个自定义反序列化器来忽略 JSON 中的某些字段,例如 here,并将成员转换为像 here 这样的数组,但在第二个链接中,我需要一个包装类,来自我的 List&lt;Member&gt;,这是我所期望的。是否可以不使用我期望的列表/数组的包装器?

【问题讨论】:

    标签: java android json gson retrofit


    【解决方案1】:

    它不是免费的,但可以通过 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);
    }
    

    输出:

    值1,值2
    值1,值2

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

    【讨论】:

      【解决方案2】:

      我相信这个小代码会对您有所帮助(假设 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;
      }
      

      【讨论】:

      • 感谢您的回复,但我不清楚如何使用new GsonBuilder().registerTypeAdapter(?.class, new DeserializerClass())注册这样的反序列化器
      • 不,您不必注册任何东西(首先,因为您的 JSON 响应具有动态数量的 member 属性和两个,您无法创建与此类配置匹配的类)...您只需调用JSON 响应的给定方法!!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-06
      相关资源
      最近更新 更多