Dedicated Server

How to use custom gson converter to parse dynamically changing json with Retrofit 2, Android


We often come across situations where our json response contains attributes that switches its type dynamically. For example , if a particular key returns JSONObject usually, but in some errror conditions it returns just an error message which is a String. In such cases, if we have configured our network callbacks to return a JSONObject then when a String is returned for error case, the network method callbacks may not be able to handle the response. In such cases we will have to perform dynamic json parsing. For such cases, you may use JsonDeserializer to intercept the response and convert it to appropriate type(using gson) even before it reaches the network method callbacks(onResponse,onFailure in case of retrofit).


Consider the following json structure in which the key responseMessage dynamically changes between a String and a JSONObject


{
"applicationType":"1",
"responseMessage":{
"surname":"Jhon",
"forename":" taylor",
"dob":"17081990",
"refNo":"3394909238490F",
"result":"Received"
}
}


{
       "applicationType":"4",
       "responseMessage":"Success"          
 }

Let us now see how to implement a custom gson converter to dynamically parse this response.

In the above cases we may create a model class like this


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ResponseWrapper {

    @SerializedName("applicationType")
    @Expose
    private String applicationType;
    @SerializedName("responseMessage")
    @Expose
    private ResponseMessage responseMessage;

    public String getApplicationType() {
        return applicationType;
    }

    public void setApplicationType(String applicationType) {
        this.applicationType = applicationType;
    }

    public ResponseMessage getResponseMessage() {
        return responseMessage;
    }

    public void setResponseMessage(ResponseMessage responseMessage) {
        this.responseMessage = responseMessage;
    }

}
where ResponseMessage will be an inner object for the json object with the same name

But this will work only when ResponseMessage is a JSONObject(the first json) but what happens if the response dynamically switches between a JSONObject and String??

One solution is that we could use  gson-converter

Dependencies/Libraries to be used :

  1. compile 'com.squareup.retrofit2:retrofit:2.3.0'
  2. compile 'com.squareup.retrofit2:converter-gson:2.3.0'

First of all you will need three model classes like the following


ResponseWrapper


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ResponseWrapper {

    @SerializedName("applicationType")
    @Expose
    private String applicationType;
    @SerializedName("responseMessage")
    @Expose
    private Object responseMessage;

    public String getApplicationType() {
        return applicationType;
    }

    public void setApplicationType(String applicationType) {
        this.applicationType = applicationType;
    }

    public Object getResponseMessage() {
        return responseMessage;
    }

    public void setResponseMessage(Object responseMessage) {
        this.responseMessage = responseMessage;
    }

}



ResponseMessage


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class ResponseMessage extends ResponseWrapper {

@SerializedName("surname")
@Expose
private String surname;
@SerializedName("forename")
@Expose
private String forename;
@SerializedName("dob")
@Expose
private String dob;
@SerializedName("refNo")
@Expose
private String refNo;
@SerializedName("result")
@Expose
private String result;

public String getSurname() {
    return surname;
}

public void setSurname(String surname) {
    this.surname = surname;
}

public String getForename() {
    return forename;
}

public void setForename(String forename) {
    this.forename = forename;
}

public String getDob() {
    return dob;
}

public void setDob(String dob) {
    this.dob = dob;
}

public String getRefNo() {
    return refNo;
}

public void setRefNo(String refNo) {
    this.refNo = refNo;
}

public String getResult() {
    return result;
}

public void setResult(String result) {
    this.result = result;
}

}


ResponseString


1
public class ResponseString extends ResponseWrapper { }

Here we have subclassed ResponseMessage, ResponseString from ResponseWrapper. The reason for this approach is that deserialize() method defined in UserResponseDeserializer have a return type of ResponseWrapper, so either of the subclassed classes can be returned from deserialize() depending on the response is an object/string

So implement a custom gson converter as shown below

UserResponseDeserializer(custom deserialiser)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class UserResponseDeserializer implements JsonDeserializer<ResponseWrapper> {
@Override
public ResponseWrapper deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {


        if (((JsonObject) json).get("responseMessage") instanceof JsonObject){
            return new Gson().fromJson(json, ResponseMessage.class);
        } else {
            return new Gson().fromJson(json, ResponseString.class);
        }

}
}

The deserialize() method is an overridden method that provides us with the json to be parsed. Here we can check the type of the json(json object/string) using instanceof method and decide whether to return ResponseMessage.class/ResponseString.class


Now pass the above UserResponseDeserializer to our GsonConverterFactory to  complete the implementation of our custom gson converter as shown below

Retrofit 2.0 Implementation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Gson userDeserializer = new GsonBuilder().setLenient().registerTypeAdapter(ResponseWrapper.class, new UserResponseDeserializer()).create();


    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("base_url")
            .addConverterFactory(GsonConverterFactory.create(userDeserializer))
            .build();


    UserService request = retrofit.create(UserService.class);
    Call<ResponseWrapper> call1=request.listAllUsers();

    call1.enqueue(new Callback<ResponseWrapper>() {
        @Override
        public void onResponse(Call<ResponseWrapper> call, Response<ResponseWrapper> response) {
            ResponseWrapper responseWrapper=response.body();
            Log.i("DYNAMIC RESPONSE", String.valueOf(response.body().getResponseMessage()));
        }

        @Override
        public void onFailure(Call<ResponseWrapper> call, Throwable t) {
        }
    });

Thats it! you can now parse a json key which dynamically switches as a json object/string. Similar procedures can be followed for json arrays as well(just check instanceof JsonArray)

You may also check this example which provides free source code download

1 comment:

  1. Hello, what in case if we have multiple model class with such dynamic jsons, do we need to add all of them to ResponseWrapper class ?

    ReplyDelete