I'm consuming an API from my android app, and all the JSON responses are like this:
{ 'status': 'OK', 'reason': 'Everything was fine', 'content': { < some data here > }
The problem is that all my POJOs have a status
, reason
fields, and inside the content
field is the real POJO I want.
Is there any way to create a custom converter of Gson to extract always the content
field, so retrofit returns the appropiate POJO?
Introduction. Gson is the main actor class of Google Gson library. It provides functionalities to convert Java objects to matching JSON constructs and vice versa. Gson is first constructed using GsonBuilder and then toJson(Object) or fromJson(String, Class) methods are used to read/write JSON constructs.
You would write a custom deserializer that returns the embedded object.
Let's say your JSON is:
{ "status":"OK", "reason":"some reason", "content" : { "foo": 123, "bar": "some value" } }
You'd then have a Content
POJO:
class Content { public int foo; public String bar; }
Then you write a 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 construct a Gson
with GsonBuilder
and register the deserializer:
Gson gson = new GsonBuilder() .registerTypeAdapter(Content.class, new MyDeserializer()) .create();
You can deserialize your JSON straight 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 the "content" field, you can make the Deserializer generic 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 have 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()
the type is carried into the deserializer, so it should then work for all your types.
And finally when creating a Retrofit instance:
Retrofit retrofit = new Retrofit.Builder() .baseUrl(url) .addConverterFactory(GsonConverterFactory.create(gson)) .build();
@BrianRoach's solution is the correct solution. It is worth noting that in the special case where you have nested custom objects that both need a custom TypeAdapter
, you must register the TypeAdapter
with the new instance of GSON, 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 wanted this JSON to be mapped to the following objects:
class MainContent { public int foo; public String bar; public SubContent subcontent; } class SubContent { public String baz; }
You would need to register the SubContent
's TypeAdapter
. To be more robust, you could 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 so:
MyDeserializer<Content> myDeserializer = new MyDeserializer<Content>(SubContent.class, new SubContentDeserializer()); Gson gson = new GsonBuilder().registerTypeAdapter(Content.class, myDeserializer).create();
This could easily be used for the nested "content" case as well by simply passing in a new instance of MyDeserializer
with null values.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With