How to handle dynamic JSON response with Retrofit

Note: This post is based on the widely used Retrofit2 networking library. Although the examples use a Gson converter, the same concept can be used with most of the other supported ones as well.

Imagine you’re in a situation where your backend can return a JSON response that’s dynamic in nature, a.k.a some parts of it don’t adhere to a specific pre-defined schema. Say you retrieve information about a webpage that you need to open in a WebView. You need to support both GET and POST HTTP requests, so two valid responses are:

{
	"url": "http://updated-website.com?product=123&size=big",
	"httpMethod": "GET"
}
{
	"url": "http://website.com",
	"httpMethod": "POST",
	"parameters" {
		"userId": "abc",
		"sessionId": "def",
		"randomParam1": "ghi",
		"randomParam2": "jkl"
	}
}

For the purpose of the example imagine that GSON can’t parse Maps automatically – the technique used here can be easily applied for more advanced examples as well. Now our problem is that we don’t know what and how many parameters there’ll be in the parameters section of the response, if present at all. Good news is that we don’t need to manipulate each individually, rather we can just put them in a Map that we’ll later pass to the WebView when making a POST request. So the Java model for our response can be:

class RedirectionInfo {
	private String uri;
	private String httpMethod;
	private Map<String, String> parameters;

    ... (constructors, getters, setters, etc.) ...
}

To read the response we can make use of Gson’s JsonDeserializer interface. It has a single deserialize() method where it’s our own responsibility to convert the incoming JSON to Java objects. Here’s a possible implementation of it:

class RedirectionInfoDeserializer implements JsonDeserializer<RedirectionInfo> {

    private static final String KEY_URI = "uri";
    private static final String KEY_METHOD = "httpMethod";
    private static final String KEY_PARAMETERS = "parameters";

    @Override
    public RedirectionInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        final JsonObject jsonObject = json.getAsJsonObject();

        // Read simple String values.
        final String uri = jsonObject.get(KEY_URI).getAsString();
        final String httpMethod = jsonObject.get(KEY_METHOD).getAsString();

        // Read the dynamic parameters object.
        final Map<String, String> parameters = readParametersMap(jsonObject);

        RedirectionInfo result = new RedirectionInfo();
        result.setUri(uri);
        result.setHttpMethod(httpMethod);
        result.setParameters(parameters);
        return result;
    }

    @Nullable
    private Map<String, String> readParametersMap(@NonNull final JsonObject jsonObject) {
        final JsonElement paramsElement = jsonObject.get(KEY_PARAMETERS);
        if (paramsElement == null) {
        	// value not present at all, just return null
            return null;
        }

        final JsonObject parametersObject = paramsElement.getAsJsonObject();
        final Map<String, String> parameters = new HashMap<>();
        for (Map.Entry<String, JsonElement> entry : parametersObject.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue().getAsString();
            parameters.put(key, value);
        }
        return parameters;
    }
}

In case you’re wondering what exactly is a JsonElement and how to parse it correctly, HERE’s a great tutorial on the subject, so definitely have a look!

Having this JsonDeserializer in place, all we need is to make use of it in Retrofit. Luckily the latter gives us the flexibility to use a custom converter to (de)serialize the request / response. The easiest way to do so is to pass a custom Gson to the GsonConverterFactory.create() method provided by Retrofit:

private Converter.Factory createGsonConverter() {
	GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(RedirectionInfo.class, new RedirectionInfoDeserializer());
    Gson gson = gsonBuilder.create();
    return GsonConverterFactory.create(gson);	
}

The third line is the most important one – it tells Gson to use our new custom deserializer every time it encounters a RedirectionInfo class. Of course we can register multiple different (de)serializers for different objects, so we can have all custom (de)serialization logic in one place.

Last step is just to use the custom factory we built:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://myserver.com")
    .addConverterFactory(createGsonConverter())
    .build();

CustomService service = retrofit.create(CustomService.class);
...

10 comments

  • M.S

    Hello Veskoiliev

    I need full source code of above sinppet syntax. Kindly provide me whole code ,So I can use this.

    Reply
    • veskoiliev

      Hey, I don’t have the full code example anymore, but it shouldn’t be hard to implement from scratch. If you have a specific question just ask and I’ll be happy to help 🙂

      Reply
      • j

        Why not post full code instead?

        Reply
  • j

    Where is the full code?

    Reply
  • Pallab

    I have JSON Array like below , can you please help in mapping the JSON Objects with retrofit , whenever i am trying to @GET from the server i am getting error “Expected BEGIN_ARRAY but was STRING at line 1 column 2 path $”

    {
    “value”: [
    {“person”: “New”, “id”: “adsadfsfe”},
    {“person”: “Open”, “id”: “ghtyrjhyj”},
    {“person”: “Close”, “id”: “hdhtrht”}
    ]
    }

    Reply
  • Caesar

    Good day please how ould it be implemented if its a json Array

    Reply
  • mj1994

    my value is array list of string in dynamic part what should i do then?

    Reply
  • Deepika

    hi,,
    deserialize method not getting called.i checked using debugger.

    Reply
  • Bagaimana cara menampilkan dinamis JSON respon dengan retrofit – kusus kotlin

  • RandomStudent

    Hello Veskoiliev

    Stumbled upon a problem recently which seems related to this and was hoping that you could help.

    In my case, I am recieving dynamic Json such that two of the fields of a nested object are returned as either a list of objects or in the case that there’s only one object it just returns that instead of list with 1 object.

    How would I go about figuring out what type of information my JsonObject holds before assigning it to the field of my object?

    Phrased in another way, how can I get the return type of field inside the JsonObject so I know whether to call JsonObject.get(KEY).getAsList() or JsonObject.get(KEY).getAsObject(MyClass.class)

    Reply

Leave a Comments

Cancel reply
NAME *
EMAIL *
Website