Organizational Research By

Surprising Reserch Topic

get nested json object with gson using retrofit


get nested json object with gson using retrofit  using -'java,android,json,gson,retrofit'

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?
    

asked Oct 23, 2015 by deven.bendale
0 votes
14 views



Related Hot Questions

3 Answers

0 votes

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
{
    @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 implements JsonDeserializer
{
    @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())
        .registerTypeAdapter(DiffContent.class, new MyDeserializer())
        .create();

When you call .fromJson() the type is carried into the deserializer, so it should then work for all your types.

answered Oct 23, 2015 by atulpariharmca
0 votes

@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 implements JsonDeserializer {
    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 myDeserializer = new MyDeserializer(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.

answered Oct 23, 2015 by okesh.badhiye
0 votes

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

 class RestDeserializer implements JsonDeserializer {

    private Class mClass;
    private String mKey;

    public RestDeserializer(Class 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 parse sample payload from above, we can register GSON deserializer:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
    .build();
answered Oct 23, 2015 by loknath.ganji

...