使用GSON获取嵌套的JSON对象

我从我的android应用程序使用API​​,所有的JSON响应是这样的:

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

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

有什么办法可以创建一个Gson自定义转换器来提取总是content字段,所以改造返回适当的POJO?

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

假设你的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); 

编辑以添加评论:

如果你有不同类型的消息,但是它们都有“content”字段,那么你可以通过执行以下操作来使Deserializer通用:

 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() ,类型会.fromJson()送到解串器,所以它应该适用于所有的类型。

BrianRoach的解决方案是正确的解决方案。 值得注意的是,在嵌套的自定义对象都需要自定义TypeAdapter的特殊情况下,必须使用TypeAdapter新实例注册TypeAdapter ,否则第二个TypeAdapter将不会被调用。 这是因为我们正在自定义解串器中创建一个新的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实例与空值。

几天前有同样的问题。 我已经解决了这个使用响应包装类和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; } } 

Rx变压器:

 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 – “全局”反序列化,改变响应类型

有点晚了,但希望这将有助于某人。

只需创建以下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()); 

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

然后从上面解析示例有效载荷,我们可以注册GSON解串器:

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

这是与@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解串器。 这是有问题的,因为密钥区分大小写,所以类名称的大小写必须与JSON密钥的大小写匹配。

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

这是一个基于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) } } 

就我而言,每个响应的“内容”键都会改变。 例:

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

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

使用注释@InnerKey("content") ,其余的代码是为了方便Gson的使用。

更好的解决方案可以是这个..

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

然后,像这样定义你的服务

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

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

萨拉姆。 不要忘记@SerializedName最反序列化的所有Class成员和Inner Class成员的@SerializedName@Expose注释。

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

由于您使用的是GSon Library,因此可以使用@SerializedName对字段进行注释:

 public class SomeClass{ @SerializedName("status") String status; @SerializedName("reason") String reason; @SerializedName("content") Content content; // add your getters and setters public static class Content{ //Your content } } 

然后,你可以在json字符串之上淡化你:

 Gson gson = new Gson(); YourObjectType obj = gson.fromJson(jsonString, YourObjectType.class); 

访问您的内容,因此:

 SomeClass.Content content = obj.getContent(); 

我希望这个作品!