How to handle Dynamic JSON in Retrofit?

AndroidJsonGsonRetrofit

Android Problem Overview


I am using the retrofit efficient networking library, but I am unable to handle Dynamic JSON which contains single prefix responseMessage which changes to object randomly, the same prefix ( responseMessage) changes to String in some cases (dynamically).

Json format Object of responseMessage:

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

responseMessage Json format dynamically changes to type string:

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

My problem is since retrofit has built-in JSON parsing, I have to assign single POJO per request! but the REST-API unfortunately, is built on dynamic JSON responses. The prefix will change from string to object randomly in both success(...) and failure(...) methods!

void doTrackRef(Map<String, String> paramsref2) {
	RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint("http://192.168.100.44/RestDemo").build();

	

	TrackerRefRequest userref = restAdapter.create(TrackerRefRequest.class);
	userref.login(paramsref2,
			new Callback<TrackerRefResponse>() {
				@Override
				public void success(
						TrackerRefResponse trackdetailresponse,
						Response response) {
					
					Toast.makeText(TrackerActivity.this, "Success",
                    Toast.LENGTH_SHORT).show();
				
				}

				@Override
				public void failure(RetrofitError retrofitError) {
					

					Toast.makeText(TrackerActivity.this, "No internet",
						Toast.LENGTH_SHORT).show();
				}

			
			});
}

Pojo:

public class TrackerRefResponse {
	
	
private String applicationType;

	private String responseMessage;          //String type
	
//private ResponseMessage responseMessage;  //Object of type ResponseMessage

//Setters and Getters

  	
}

In above code POJO TrackerRefResponse.java prefix responseMessage is set to string or object of type responseMessage , so we can create the POJO with ref variable with same name (java basics :) ) so I'm looking for same solution for dynamic JSON in Retrofit. I know this is very easy job in normal http clients with async task, but it's not the best practice in the REST-Api JSON parsing! looking at the performance Benchmarks always Volley or Retrofit is the best choice, but I'm failed handle dynamic JSON!

Possible solution I Know

  1. Use old asyc task with http client parsing. :(

  2. Try to convince the RESTapi backend developer.

  3. Create custom Retrofit client :)

Android Solutions


Solution 1 - Android

Late to the party, but you can use a converter.

RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint("https://graph.facebook.com")
    .setConverter(new DynamicJsonConverter()) // set your static class as converter here
    .build();

api = restAdapter.create(FacebookApi.class);

Then you use a static class which implements retrofit's Converter:

static class DynamicJsonConverter implements Converter {

    @Override public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {
        try {
            InputStream in = typedInput.in(); // convert the typedInput to String
            String string = fromStream(in);
            in.close(); // we are responsible to close the InputStream after use

            if (String.class.equals(type)) {
                return string;
            } else {
                return new Gson().fromJson(string, type); // convert to the supplied type, typically Object, JsonObject or Map<String, Object>
            }
        } catch (Exception e) { // a lot may happen here, whatever happens
            throw new ConversionException(e); // wrap it into ConversionException so retrofit can process it
        }
    }

    @Override public TypedOutput toBody(Object object) { // not required
        return null;
    }

    private static String fromStream(InputStream in) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        StringBuilder out = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            out.append(line);
            out.append("\r\n");
        }
        return out.toString();
    }
}

I have written this sample converter so it returns the Json response either as String, Object, JsonObject or Map< String, Object >. Obviously not all return types will work for every Json, and there is sure room for improvement. But it demonstrates how to use a Converter to convert almost any response to dynamic Json.

Solution 2 - Android

RestClient.java

import retrofit.client.Response;
public interface RestClient {
  @GET("/api/foo") Response getYourJson();
}

YourClass.java

RestClient restClient;

// create your restClient

Response response = restClient.getYourJson();

Gson gson = new Gson();
String json = response.getBody().toString();
if (checkResponseMessage(json)) {
  Pojo1 pojo1 = gson.fromJson(json, Pojo1.class);
} else {
  Pojo2 pojo2 = gson.fromJson(json, Pojo2.class);
}

You must implement "checkResponseMessage" method.

Solution 3 - Android

Try custom deserialisation using gson-converter as below(updated answer for Retrofit 2.0)

Create three models as shown below

ResponseWrapper

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

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

public class ResponseString extends ResponseWrapper {

}

UserResponseDeserializer(custom deserialiser)

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);
        }

}
}

Retrofit 2.0 Implementation

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) {
        }
    });

Libraries Used

> compile 'com.squareup.retrofit2:retrofit:2.3.0'

> compile 'com.squareup.retrofit2:converter-gson:2.3.0'

***** Previous Answer (above answer is more recommended one) *****

Change your pojo like this

public class TrackerRefResponse {

  private String applicationType;
  private Object responseMessage;

  public Object getResponseMessage() {
  return responseMessage;
  }

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

and change retrofit's onResponse like this

 @Override
public void onResponse(Response<TrackerRefResponse > response) {
    if (response.isSuccess()) {
        if (response.getResponseMessage() instanceof String)
            {
            handleStringResponse();
         }
        else 
            {
            handleObjectResponse();
         }
    }
}

you may also check this post for more details about dynamic json parsing

Solution 4 - Android

Any of your possible solutions will work. What you can also do is send the Retrofit api interface return type to response. With that response you get a body Inputstream which you can convert to a JSON Object and read as you see fit.

Look at: http://square.github.io/retrofit/#api-declaration - under RESPONSE OBJECT TYPE

Updated

Retrofit 2 is out now and with it some changes to the documentation and library.

Look at http://square.github.io/retrofit/#restadapter-configuration there are request and response body object that can be used.

Solution 5 - Android

The accepted answer seemed over complicated for me, I solve it this way:

Call<ResponseBody> call = client.request(params);
call.enqueue(new Callback<ResponseBody>() {
	@Override
	public void onResponse(Response<ResponseBody> response) {
		if (response.isSuccess()) {
			Gson gson = new Gson();
			ResponseBody repsonseBody = response.body().string();
			if (isEmail) {
				EmailReport reports = gson.fromJson(responseBody, EmailReport.class);
			} else{
				PhoneReport reports = gson.fromJson(repsonseBody, PhoneReport.class);
			}
		}
	}
	@Override
	public void onFailure(Throwable t) {
		Log.e(LOG_TAG, "message =" + t.getMessage());
	}
});

This is just an example in attempt to show you how you can use different model.

The variable isEmail is just a boolean for your condition to use the appropriate model.

Solution 6 - Android

I know I am very very late to the party. I had a similar issue and just solved it like this:

public class TrackerRefResponse {

    private String applicationType;
    // Changed to Object. Works fine with String and array responses.
    private Object responseMessage;

}

I literally just changed to type to Object. I chose this approach because only one field in the response was dynamic (for me, my response was way more complicated), so using a converter would have made life difficult. Used Gson to work with the Object from there, depending on if it was a String or Array value.

Hope this helps someone looking for a simple answer :).

Solution 7 - Android

If it was not possible to change the backend API, I would consider the following variants (if Gson is used to convert JSON).

  1. We can use Gson type adapters to create a custom adapter for ResponseMessage type that dynamically decides how to parse the inoming JSON (using something like if (reader.peek() == JsonToken.STRING)).

  2. Put some meta information describing the response type into an HTTP header and use it to determine what type information must be fed to Gson instance.

Solution 8 - Android

For kotlin developers you can declare the type of your variable as Any then convert it to Gson class with gson converter

data class SomeClass(
     ...somevariables,
     val respnseMessage : Any
)

Then in your activity or fragment wherever you want to parse and use it. based on your use case you can do something like this

 val gson = Gson()
 val data = gson.fromJson<YourDesireClass> 
 (response.responseMessage.toString() , YourDesireClass::class.java)

Solution 9 - Android

In addition to what you told -

Use Callback Then you can retrieve the fields using regular get method. For more information, go through the javadoc of gson.

http://google-gson.googlecode.com/svn/tags/1.2.3/docs/javadocs/com/google/gson/JsonObject.html

Solution 10 - Android

I know I am late, but I just want to share my thought. I was working on a project where I am writing a method. The method uses retrofit to get data from server. Since other developers in my company will use this method, I could not use a POJO class (in your example, the TrackerRefResponse class). So I used JsonObject / Object like this:

interface APIService.java

public class APIService{
    @FormUrlEncoded
    @POST
    Call<JsonObject> myPostMethod(@Url String url, @Field("input") String input);
}

Then in my method, I wrote this:

Model1 model1 = null;
Model2 model2 = null;
Call<JsonObject> call = RetrofitClient.getAPIService().establishUserSession(post_request_url, someParameter);

call.enqueue(new Callback<JsonObject>() {
            @Override
            public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
                JsonObject jsonObject = response.body();
                // then do your stuff. maybe something like this
  try{
    model1 = new Gson().fromJson(jsonObject, Model1.class);
  }catch(Exception x){}
  try{
    model2 = new Gson().fromJson(jsonObject, Model2.class);
  }catch(Exception x){}  

  if(model1 != null) { /*handle model1 */}
  if(model2 != null) { /*handle model2*/}
 // rest of the code
}
        

If you know that a certain attibute will tell you which type of response it is, you can use JsonObject , read that attribute and then cast the model like this way:

// ... retrofit codes 
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
  int number = jsonObject.get("applicationType").getAsInt();
  if(number == 1) {
    model1 = new Gson().fromJson(jsonObject, Model1.class);
  }
}
// ... rest of the code

You can also use Object instead of 'JsonObject`. Later, when you will know which kind of response it is, maybe you can cast this into desired object.

Solution 11 - Android

I too ran of this issue. but i am not sure if this was your case , (i am using Retrofit2)

on my case i need to handle error, and success messages.

On Success

{
"call_id": 1,
"status": "SUCCESS",
"status_code": "SUCCESS",
"result": {
    "data1": {
        "id": "RFP2UjW7p8ggpMXzYO9tRg==",
        "name": "abcdef",
        "mobile_no": "96655222",
        "email": ""
    },
    "data2": [
        {
            "no": "12345"
        },
        {
            "no": "45632"
        }
    ]
}
}

On Error,

{
"call_id": 1,
"status": "FAILED",
"status_code": "NO_RECORDS",
"error": {
    "error_title": "xxx",
    "error_message": "details not found"
}
}

for this i just created another POJO Error,

public class ValidateUserResponse {
@SerializedName("call_id")
public String callId;
@SerializedName("status")
public String status;
@SerializedName("status_code")
public String statusCode;
@SerializedName("result")
public ValidateUserResult result;
@SerializedName("error")
public Error error;
}

Error.java

public class Error {
@SerializedName("error_title")
public String errorTitle;
@SerializedName("error_message")
public String errorMessage;
}

ValidateUser.java

public class ValidateUserResult {

@SerializedName("auth_check")
public String authCheck;
@SerializedName("data1")
public Data1 data1;
@SerializedName("data2")
public List<Data2> data2;
}

in the above case if the result key on json contains data1,data2 then the ValidateUserResult.java get initialised. if error then the Error.java class get initialized.

Solution 12 - Android

Just look into the other options it works for me:-

Json

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

}

2.
 {
	   "applicationType":"4",
	   "responseMessage":
	{
	   "forename":" taylor",
	   "dob":"17081990",
	}	   
 }
3.
{
	   "applicationType":"5",
	   "responseMessage":
	{
	   "refNo":"3394909238490F",
	   "result":"Received"
	}	   
 }

Pojo class will be:-

public class ResponseMessage
{
	private String surname;

	private String forename;

	private String dob;

	private String refNo;

	private String result;

	public void setSurname(String surname){
		this.surname = surname;
	}
	public String getSurname(){
		return this.surname;
	}
	public void setForename(String forename){
		this.forename = forename;
	}
	public String getForename(){
		return this.forename;
	}
	public void setDob(String dob){
		this.dob = dob;
	}
	public String getDob(){
		return this.dob;
	}
	public void setRefNo(String refNo){
		this.refNo = refNo;
	}
	public String getRefNo(){
		return this.refNo;
	}
	public void setResult(String result){
		this.result = result;
	}
	public String getResult(){
		return this.result;
	}
}

  public class Root
   {
	private String applicationType;

	private ResponseMessage responseMessage;

	public void setApplicationType(String applicationType){
		this.applicationType = applicationType;
	}
	public String getApplicationType(){
		return this.applicationType;
	}
	public void setResponseMessage(ResponseMessage responseMessage){
		this.responseMessage = responseMessage;
	}
	public ResponseMessage getResponseMessage(){
		return this.responseMessage;
	}
 }

now final code

 if(responseMessage.getSurname() !=null){
  ---do something---
 }
 if(responseMessage.getForename !=null){
   ----do something
 }
 if(responseMessage.getDob() !=null){
  ---do something---
 }
 if(responseMessage.getRefNo() !=null){
   ---do something---
 }
 if(responseMessage.getResult() !=null){
   ---do something---
 }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLOG_TAGView Question on Stackoverflow
Solution 1 - AndroidOliver HauslerView Answer on Stackoverflow
Solution 2 - AndroidYuki YoshidaView Answer on Stackoverflow
Solution 3 - AndroidNavneet KrishnaView Answer on Stackoverflow
Solution 4 - AndroidJustin SladeView Answer on Stackoverflow
Solution 5 - AndroidmedaView Answer on Stackoverflow
Solution 6 - AndroidLunchboxView Answer on Stackoverflow
Solution 7 - AndroidRoman MazurView Answer on Stackoverflow
Solution 8 - Androidmmdreza baqalpourView Answer on Stackoverflow
Solution 9 - AndroidSugeshView Answer on Stackoverflow
Solution 10 - AndroidQazi Fahim FarhanView Answer on Stackoverflow
Solution 11 - AndroidAkhil T MohanView Answer on Stackoverflow
Solution 12 - AndroidAvnish KumarView Answer on Stackoverflow