How to handle Dynamic JSON in Retrofit?
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.
Related videos on Youtube
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, 2022Comments
-
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 toobject
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 dynamicJSON
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-ApiJSON
parsing! looking at the performance Benchmarks always Volley or Retrofit is the best choice, but I'm failed handle dynamicJSON
!Possible solution I Know
Use old asyc task with http client parsing. :(
Try to convince the RESTapi backend developer.
Create custom Retrofit client :)
-
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 about 8 yearsCould you please elaborate that? This code is non descriptive. Where does mType come from?
-
meda about 8 years@Desdroid I simplified the code and then expanded with explanation
-
Desdroid about 8 yearsStill, 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 about 8 yearsI 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 about 8 yearsI'm afraid I can not find the section you have provided,are there any synonym?
-
androidtitan about 8 yearsSeeing
RestAdapter
this example is for Retrofit 1. How would you implement the same converter in Retrofit 2? -
Amt87 almost 8 yearsI think it will not work because Deserializer will throw an exception and reach the onFailure() not onResponse()
-
mutkan over 7 yearsConversionException is not available in Retrofit 2 :(
-
RabbitBones22 over 5 yearsIs 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 over 5 yearsyou may avoid the wrapper class if its confusing and try this freshbytelabs.com/2018/12/…
-
Vadim Kotov almost 5 yearsHow can I do the same for in Retrofit 2?
-
Shashank singh almost 5 yearsWhat is "checkResponseMessage" ?
-
YazidEF over 3 yearsThis 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.