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 Map
s 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 JsonDeserializerdeserialize()
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
7 years agoHello Veskoiliev
I need full source code of above sinppet syntax. Kindly provide me whole code ,So I can use this.
veskoiliev
7 years agoHey, 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 🙂
j
7 years agoWhy not post full code instead?
j
7 years agoWhere is the full code?
Pallab
6 years agoI 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”}
]
}
Caesar
6 years agoGood day please how ould it be implemented if its a json Array
mj1994
6 years agomy value is array list of string in dynamic part what should i do then?
Deepika
6 years agohi,,
deserialize method not getting called.i checked using debugger.
Bagaimana cara menampilkan dinamis JSON respon dengan retrofit – kusus kotlin
RandomStudent
5 years agoHello 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)