Get nested JSON object with GSON using retrofit Get nested JSON object with GSON using retrofit java java

Get nested JSON object with GSON using retrofit


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.


Bit late but hopefully this will help someone.

Just create 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 into your GSON builder :

.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

or

 yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());