【问题标题】:Get nested JSON object with GSON using retrofit使用改造获取带有 GSON 的嵌套 JSON 对象
【发布时间】:2014-05-29 00:22:08
【问题描述】:

我正在使用我的 android 应用程序中的 API,所有 JSON 响应都是这样的:

{
    'status': 'OK',
    'reason': 'Everything was fine',
    'content': {
         < some data here >
}

问题是我所有的 POJO 都有一个 statusreason 字段,而在 content 字段里面是我想要的真正的 POJO。

有什么方法可以创建一个 Gson 的自定义转换器来始终提取 content 字段,因此改造返回适当的 POJO?

【问题讨论】:

  • 我阅读了文档,但我不知道该怎么做... :( 我不知道如何编写代码来解决我的问题
  • 我很好奇你为什么不只是格式化你的 POJO 类来处理这些状态结果。

标签: java android json gson retrofit


【解决方案1】:

您将编写一个返回嵌入对象的自定义反序列化器。

假设您的 JSON 是:

{
    "status":"OK",
    "reason":"some reason",
    "content" : 
    {
        "foo": 123,
        "bar": "some value"
    }
}

然后你会有一个Content POJO:

class Content
{
    public int foo;
    public String bar;
}

然后你写一个反序列化器:

class MyDeserializer implements JsonDeserializer<Content>
{
    @Override
    public Content deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
        throws JsonParseException
    {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        return new Gson().fromJson(content, Content.class);

    }
}

现在如果你用GsonBuilder 构造一个Gson 并注册反序列化器:

Gson gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer())
        .create();

您可以将 JSON 直接反序列化为您的 Content

Content c = gson.fromJson(myJson, Content.class);

编辑以从 cmets 添加:

如果您有不同类型的消息,但它们都具有“内容”字段,则可以通过以下操作使反序列化器通用:

class MyDeserializer<T> implements JsonDeserializer<T>
{
    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
        throws JsonParseException
    {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        return new Gson().fromJson(content, type);

    }
}

您只需为每种类型注册一个实例:

Gson gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer<Content>())
        .registerTypeAdapter(DiffContent.class, new MyDeserializer<DiffContent>())
        .create();

当您调用.fromJson() 时,该类型被带入反序列化器,因此它应该适用于您的所有类型。

最后在创建 Retrofit 实例时:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

【讨论】:

  • 哇,太棒了!谢谢! :D 有什么方法可以概括解决方案,这样我就不必为每种类型的响应创建一个 JsonDeserializer?
  • 这太棒了!一件事要改变: Gson gson = new GsonBuilder().create();而不是 Gson gson = new GsonBuilder().build();有两个例子。
  • @feresr 你可以在 Retrofit 的RestAdapter.Builder 类中调用setConverter(new GsonConverter(gson))
  • @BrianRoach 谢谢,很好的回答.. 我应该用分离的解串器注册Person.classList&lt;Person&gt;.class/Person[].class 吗?
  • 还有可能获得“状态”和“原因”吗?例如,如果所有请求都返回它们,我们是否可以将它们放在超类中并使用来自“内容”的实际 POJO 的子类?
【解决方案2】:

@BrianRoach 的解决方案是正确的解决方案。值得注意的是,在嵌套自定义对象且都需要自定义TypeAdapter 的特殊情况下,您必须将TypeAdapter 注册到GSON 的新实例,否则第二个@987654323 @ 永远不会被调用。这是因为我们在自定义反序列化器中创建了一个新的 Gson 实例。

例如,如果您有以下 json:

{
    "status": "OK",
    "reason": "some reason",
    "content": {
        "foo": 123,
        "bar": "some value",
        "subcontent": {
            "useless": "field",
            "data": {
                "baz": "values"
            }
        }
    }
}

并且您希望将此 JSON 映射到以下对象:

class MainContent
{
    public int foo;
    public String bar;
    public SubContent subcontent;
}

class SubContent
{
    public String baz;
}

您需要注册SubContentTypeAdapter。为了更健壮,您可以执行以下操作:

public class MyDeserializer<T> implements JsonDeserializer<T> {
    private final Class mNestedClazz;
    private final Object mNestedDeserializer;

    public MyDeserializer(Class nestedClazz, Object nestedDeserializer) {
        mNestedClazz = nestedClazz;
        mNestedDeserializer = nestedDeserializer;
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        GsonBuilder builder = new GsonBuilder();
        if (mNestedClazz != null && mNestedDeserializer != null) {
            builder.registerTypeAdapter(mNestedClazz, mNestedDeserializer);
        }
        return builder.create().fromJson(content, type);

    }
}

然后像这样创建它:

MyDeserializer<Content> myDeserializer = new MyDeserializer<Content>(SubContent.class,
                    new SubContentDeserializer());
Gson gson = new GsonBuilder().registerTypeAdapter(Content.class, myDeserializer).create();

这也可以很容易地用于嵌套的“内容”案例,只需传入一个带有空值的 MyDeserializer 的新实例。

【讨论】:

  • “类型”来自哪个包?有一百万个包包含“类型”类。谢谢。
  • @Mr.Tea 会是java.lang.reflect.Type
  • SubContentDeserializer 类在哪里? @KMarlow
【解决方案3】:

有点晚了,但希望这会对某人有所帮助。

只需创建以下 TypeAdapterFactory。

    public class ItemTypeAdapterFactory implements TypeAdapterFactory {

      public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {

        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);

        return new TypeAdapter<T>() {

            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            public T read(JsonReader in) throws IOException {

                JsonElement jsonElement = elementAdapter.read(in);
                if (jsonElement.isJsonObject()) {
                    JsonObject jsonObject = jsonElement.getAsJsonObject();
                    if (jsonObject.has("content")) {
                        jsonElement = jsonObject.get("content");
                    }
                }

                return delegate.fromJsonTree(jsonElement);
            }
        }.nullSafe();
    }
}

并将其添加到您的 GSON 构建器中:

.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

 yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

【讨论】:

  • 这正是我的样子。因为我有很多用“数据”节点包装的类型,我不能将 TypeAdapter 添加到每个类型。谢谢!
  • @SergeyIrisov 欢迎您。你可以投票给这个答案,让它变得更高:)
  • 如何传递多个jsonElement ?.比如我有contentcontent1等。
  • 我认为你的后端开发人员应该改变结构,不要传递内容,content1...这种方法有什么优势?
  • 谢谢!这是完美的答案。 @Marin Petrulak:优点是这种形式可以适应未来的变化。 “内容”是响应内容。未来它们可能会出现新的字段,如“version”、“lastUpdated”、“sessionToken”等。如果你没有事先包装你的响应内容,你会在你的代码中遇到一堆变通方法来适应新的结构。
【解决方案4】:

几天前遇到了同样的问题。我已经使用响应包装类和 RxJava 转换器解决了这个问题,我认为这是非常灵活的解决方案:

包装器:

public class ApiResponse<T> {
    public String status;
    public String reason;
    public T content;
}

当状态不正常时抛出自定义异常:

public class ApiException extends RuntimeException {
    private final String reason;

    public ApiException(String reason) {
        this.reason = reason;
    }

    public String getReason() {
        return apiError;
    }
}

接收变压器:

protected <T> Observable.Transformer<ApiResponse<T>, T> applySchedulersAndExtractData() {
    return observable -> observable
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .map(tApiResponse -> {
                if (!tApiResponse.status.equals("OK"))
                    throw new ApiException(tApiResponse.reason);
                else
                    return tApiResponse.content;
            });
}

使用示例:

// Call definition:
@GET("/api/getMyPojo")
Observable<ApiResponse<MyPojo>> getConfig();

// Call invoke:
webservice.getMyPojo()
        .compose(applySchedulersAndExtractData())
        .subscribe(this::handleSuccess, this::handleError);


private void handleSuccess(MyPojo mypojo) {
    // handle success
}

private void handleError(Throwable t) {
    getView().showSnackbar( ((ApiException) throwable).getReason() );
}

我的话题: Retrofit 2 RxJava - Gson - "Global" deserialization, change response type

【讨论】:

  • MyPojo 长什么样子?
  • @IgorGanapolsky MyPojo 可以随心所欲。它应该与从服务器检索到的内容数据相匹配。此类的结构应根据您的序列化转换器(Gson、Jackson 等)进行调整。
  • @rafakob 你能帮我解决我的问题吗?很难以最简单的方式尝试在我的嵌套 json 中获取一个字段。这是我的问题:stackoverflow.com/questions/56501897/…
【解决方案5】:

继续 Brian 的想法,因为我们几乎总是有许多 REST 资源,每个资源都有自己的根,所以概括反序列化可能很有用:

 class RestDeserializer<T> implements JsonDeserializer<T> {

    private Class<T> mClass;
    private String mKey;

    public RestDeserializer(Class<T> targetClass, String key) {
        mClass = targetClass;
        mKey = key;
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        JsonElement content = je.getAsJsonObject().get(mKey);
        return new Gson().fromJson(content, mClass);

    }
}

然后为了解析上面的样本payload,我们可以注册GSON反序列化器:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
    .build();

【讨论】:

    【解决方案6】:

    更好的解决方案可能是这样..

    public class ApiResponse<T> {
        public T data;
        public String status;
        public String reason;
    }
    

    然后,像这样定义您的服务..

    Observable<ApiResponse<YourClass>> updateDevice(..);
    

    【讨论】:

      【解决方案7】:

      根据@Brian Roach 和@rafakob 的回答,我通过以下方式完成此操作

      来自服务器的 JSON 响应

      {
        "status": true,
        "code": 200,
        "message": "Success",
        "data": {
          "fullname": "Rohan",
          "role": 1
        }
      }
      

      通用数据处理类

      public class ApiResponse<T> {
          @SerializedName("status")
          public boolean status;
      
          @SerializedName("code")
          public int code;
      
          @SerializedName("message")
          public String reason;
      
          @SerializedName("data")
          public T content;
      }
      

      自定义序列化程序

      static class MyDeserializer<T> implements JsonDeserializer<T>
      {
           @Override
            public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
                          throws JsonParseException
            {
                JsonElement content = je.getAsJsonObject();
      
                // Deserialize it. You use a new instance of Gson to avoid infinite recursion
                // to this deserializer
                return new Gson().fromJson(content, type);
      
            }
      }
      

      Gson 对象

      Gson gson = new GsonBuilder()
                          .registerTypeAdapter(ApiResponse.class, new MyDeserializer<ApiResponse>())
                          .create();
      

      API调用

       @FormUrlEncoded
       @POST("/loginUser")
       Observable<ApiResponse<Profile>> signIn(@Field("email") String username, @Field("password") String password);
      
      restService.signIn(username, password)
                      .observeOn(AndroidSchedulers.mainThread())
                      .subscribeOn(Schedulers.io())
                      .subscribe(new Observer<ApiResponse<Profile>>() {
                          @Override
                          public void onCompleted() {
                              Log.i("login", "On complete");
                          }
      
                          @Override
                          public void onError(Throwable e) {
                              Log.i("login", e.toString());
                          }
      
                          @Override
                          public void onNext(ApiResponse<Profile> response) {
                               Profile profile= response.content;
                               Log.i("login", profile.getFullname());
                          }
                      });
      

      【讨论】:

        【解决方案8】:

        这与@AYarulin 的解决方案相同,但假设类名是 JSON 键名。这样你只需要传递类名。

         class RestDeserializer<T> implements JsonDeserializer<T> {
        
            private Class<T> mClass;
            private String mKey;
        
            public RestDeserializer(Class<T> targetClass) {
                mClass = targetClass;
                mKey = mClass.getSimpleName();
            }
        
            @Override
            public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
                    throws JsonParseException {
                JsonElement content = je.getAsJsonObject().get(mKey);
                return new Gson().fromJson(content, mClass);
        
            }
        }
        

        然后为了从上面解析样本有效载荷,我们可以注册 GSON 反序列化器。这是有问题的,因为 Key 区分大小写,因此类名的大小写必须与 JSON 密钥的大小写匹配。

        Gson gson = new GsonBuilder()
        .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class))
        .build();
        

        【讨论】:

          【解决方案9】:

          这是基于 Brian Roach 和 AYarulin 回答的 Kotlin 版本。

          class RestDeserializer<T>(targetClass: Class<T>, key: String?) : JsonDeserializer<T> {
              val targetClass = targetClass
              val key = key
          
              override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): T {
                  val data = json!!.asJsonObject.get(key ?: "")
          
                  return Gson().fromJson(data, targetClass)
              }
          }
          

          【讨论】:

            【解决方案10】:

            在我的例子中,每个响应的“内容”键都会改变。示例:

            // Root is hotel
            {
              status : "ok",
              statusCode : 200,
              hotels : [{
                name : "Taj Palace",
                location : {
                  lat : 12
                  lng : 77
                }
            
              }, {
                name : "Plaza", 
                location : {
                  lat : 12
                  lng : 77
                }
              }]
            }
            
            //Root is city
            
            {
              status : "ok",
              statusCode : 200,
              city : {
                name : "Vegas",
                location : {
                  lat : 12
                  lng : 77
                }
            }
            

            在这种情况下,我使用了上面列出的类似解决方案,但不得不对其进行调整。你可以看到要点here。在 SOF 上发布它有点太大了。

            使用注解@InnerKey("content"),其余代码方便与Gson配合使用。

            【讨论】:

            【解决方案11】:

            不要忘记 GSON 从 JSON 反序列化最多的所有类成员和内部类成员的 @SerializedName@Expose 注释。

            https://stackoverflow.com/a/40239512/1676736

            【讨论】:

              【解决方案12】:

              另一个简单的解决方案:

              JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString);
              Content content = gson.fromJson(parsed.get("content"), Content.class);
              

              【讨论】:

                【解决方案13】:

                还有一个更简单的方法,只要把content子对象当作另一个类:

                class Content {
                    var foo = 0
                    var bar: String? = null
                }
                
                class Response {
                    var statis: String? = null
                    var reason: String? = null
                    var content: Content? = null
                } 
                

                现在您可以使用Response 类型来反序列化json。

                【讨论】:

                  猜你喜欢
                  • 2016-07-07
                  • 2018-09-01
                  • 2015-05-15
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-05-11
                  相关资源
                  最近更新 更多