Get a nested JSON object using GSON using a modified

I am using the API from my Android application and all JSON responses look like this:

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

The problem is that all my POJOs have status , reason fields, and inside the content field is the real POJO that I want.

Is there a way to create a custom Gson converter to always retrieve the content field, so re-equipping returns a suitable POJO?

+101
java json android gson retrofit
Apr 14 '14 at 20:59
source share
13 answers

You must write your own deserializer that returns an inline object.

Let's say your JSON is:

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

Then you will have Content POJO:

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

Then you write the deserializer:

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

Now, if you are Gson using GsonBuilder and register a deserializer:

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

You can deserialize your JSON directly to your Content :

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

Edit to add from comments:

If you have different types of messages, but they all have a β€œcontent” field, you can make a universal deserializer by doing:

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

You just need to register an instance for each of your types:

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

When you call .fromJson() type is transferred to the deserializer, so it should work for all of your types.

And finally, when creating the Retrofit instance:

 Retrofit retrofit = new Retrofit.Builder() .baseUrl(url) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); 
+156
Apr 14 '14 at 21:50
source share

@BrianRoach's solution is the right solution. It is worth noting that in the special case, when you have nested user objects that require a custom TypeAdapter , you must register the TypeAdapter with a new GSON instance , otherwise the second TypeAdapter will never be called. This is because we are creating a new Gson instance inside our custom deserializer.

For example, if you had the following json:

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

And you want this JSON to map to the following objects:

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

You will need to register a SubContent TypeAdapter . To be more reliable, you can do the following:

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

and then create it like this:

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

This can easily be used for nested "content" by simply skipping a new instance of MyDeserializer with null values.

+15
Jan 16 '15 at 1:45
source share

A little late, but hopefully this helps someone.

Just create the following 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(); } } 

and add it to your GSON constructor:

 .registerTypeAdapterFactory(new ItemTypeAdapterFactory()); 

or

  yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory()); 
+9
Nov 19 '16 at 10:24
source share

Continuing Brian’s idea, because we almost always have many REST resources, each with its own root, it may be useful to generalize deserialization:

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

Then, to analyze the payload of the sample from above, we can register the GSON deserializer:

 Gson gson = new GsonBuilder() .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content")) .build(); 
+7
Dec 15 '14 at 21:13
source share

There was the same problem a couple of days ago. I solve this with the response wrapper class and the RxJava transformer, which I think is a pretty flexible solution:

Packing:

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

Custom exception for throw when state is out of order:

 public class ApiException extends RuntimeException { private final String reason; public ApiException(String reason) { this.reason = reason; } public String getReason() { return apiError; } } 

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

Using an example:

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

My topic: Retrofitting 2 RxJava - Gson - "Global" deserialization, changing response type

+6
Dec 19 '15 at 21:05
source share

A better solution might be this.

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

Then define your service as follows.

 Observable<ApiResponse<YourClass>> updateDevice(..); 
+3
Oct 26 '16 at 5:02
source share

According to the answer of @Brian Roach and @rafakob, I did it as follows

Json response from server

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

Generic Data Handler Class

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

Custom serializer

 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 object

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

Api call

  @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()); } }); 
+3
May 19 '17 at 11:44
source share

This is the same solution as @AYarulin, but suppose the class name is the name of the JSON key. Thus, you only need to pass the class name.

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

Then, to analyze the sample payload from above, we can register the GSON deserializer. This is problematic because the key is case sensitive, so the case of the class name must match the case of the JSON key.

 Gson gson = new GsonBuilder() .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class)) .build(); 
+2
Dec 6 '15 at 2:53 on
source share

Here's a version of Kotlin, based on the answers of Brian Roach and A. Yarulin.

 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) } } 
+2
Jan 19 '17 at 20:31 on
source share

In my case, the key "content" will change for each response. Example:

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

In such cases, I used a similar solution, as described above, but had to fine-tune it. You can see the gist here . It is too big to post here on SOF.

The @InnerKey("content") annotation is used, and the rest of the code is intended to facilitate its use with Gson.

+1
Mar 09 '16 at 14:36
source share

Remember the @SerializedName and @Expose for all class members and inner class members that are most deserialized from GSON JSON.

Have a look at https://stackoverflow.com/a/16815/

0
Oct. 25 '16 at 12:08
source share

Since you are using the GSon library, you can annotate the field using @SerializedName this way:

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

And then you can refute you above the json line this way:

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

Access your content this way:

 SomeClass.Content content = obj.getContent(); 

Hope it works!

0
Sep 28 '17 at 15:33
source share

Another simple solution:

 JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString); Content content = gson.fromJson(parsed.get("content"), Content.class); 
0
Jun 07 '19 at 11:19
source share



All Articles