How to handle Dynamic JSON in Retrofit?

66,699

Solution 1

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

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

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

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

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.

Share:
66,699

Related videos on Youtube

LOG_TAG
Author by

LOG_TAG

Android app developer, enthusiast and #SOreadytohelp Interests: Any thing related to Android with new API's which enhances the UI optimization,backend optimization according to rapidly changing APIs! User engagement with modern UI patterns with Google Guidelines for building the app on all Devices. Rooting, trying out different Custom ROMs,helping the friends,Colleagues to install custom ROMs. Collecting latest android open-source android-libraries with rapidly changing mobile place. with this I can find quick solution to any new task or complex problem in the dev life.learning advanced concepts like RxAndroid,Dagger2,MVMM etc This is How I code: Lessons Learned: “Better than a thousand days of diligent study is one day with a great teacher or Meetup or Just Google and reverse Engineer it " "Anyone can learn anything and become good at it!" ~Vikram Shastry (@uttarainfo)Java Guru. Quotes that like in Android dev life: 'I choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it!'~Bill Gates 'Be a lazy but a productive android developer!'~Paresh Mayani Dreams: Trying put my nose in all new aspects of App Android Dev, learn them quickly. Wanted to be in a place where there is some value for implementing best practices in the mobile platform 'Dream is not that which you see while sleeping it is something that does not let you sleep.' ~A P J Abdul Kalam Unhappy about: Forcing Android Dev to enforce iOS UI models in Android. We need follow this forever: 'Our app needs to look consistent across all platforms' = our app looks wrong' ~Benedict Evans 'I don't believe that inter-platform consistency between an app's different versions is as important than consistency between apps on a single platform. Not many users use multiple different phones at the same time.' ~ @lehtimaeki Some more texts: I don't want lose my old stackoverflow DP! Few of my Answers reached 100+ votes:)

Updated on March 17, 2022

Comments

  • LOG_TAG
    LOG_TAG about 2 years

    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 :)

    • mrx
      mrx about 4 years
      "Try to convince the RESTapi backend developer." did the trick for me! lol! ;) (n.b: I was the backend dev too, me to convince myself!)
  • Desdroid
    Desdroid about 8 years
    Could you please elaborate that? This code is non descriptive. Where does mType come from?
  • meda
    meda about 8 years
    @Desdroid I simplified the code and then expanded with explanation
  • Desdroid
    Desdroid about 8 years
    Still, I believe this won't help if you don't know the response Type before doing the call, which is the case in the question. Sure you could first get the InputStream of the Response Body, read a few lines, determine of which Type the Body is and then convert it. But it's just not as simple as that.
  • Desdroid
    Desdroid about 8 years
    I am looking for the best way to go in handling different return types. Your answer looked pretty promising, but I wasn't sure where you knew the Type from. That's why I wanted you to elaborate that ;)
  • zionpi
    zionpi about 8 years
    I'm afraid I can not find the section you have provided,are there any synonym?
  • androidtitan
    androidtitan about 8 years
    Seeing RestAdapter this example is for Retrofit 1. How would you implement the same converter in Retrofit 2?
  • Amt87
    Amt87 almost 8 years
    I think it will not work because Deserializer will throw an exception and reach the onFailure() not onResponse()
  • mutkan
    mutkan over 7 years
    ConversionException is not available in Retrofit 2 :(
  • RabbitBones22
    RabbitBones22 over 5 years
    Is the ResponseWrapper class truly necessary? I think it looks very confusing. I need a converter on anything but the object highest in the hierarchy...
  • Navneet Krishna
    Navneet Krishna over 5 years
    you may avoid the wrapper class if its confusing and try this freshbytelabs.com/2018/12/…
  • Vadim Kotov
    Vadim Kotov almost 5 years
    How can I do the same for in Retrofit 2?
  • Shashank singh
    Shashank singh almost 5 years
    What is "checkResponseMessage" ?
  • YazidEF
    YazidEF over 3 years
    This solution fit my problem better. One of api response with null if everything is ok, and return success (code 200) with error message in simple Json object when there is issue. I use JsonNull as default response.