Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

retrofit gson converter for nested json with different objects

I've JSON structure like follows -

{
    "status": true,
    "message": "Registration Complete.",
    "data": {
        "user": {
            "username": "user88",
            "email": "[email protected]",
            "created_on": "1426171225",
            "last_login": null,
            "active": "1",
            "first_name": "User",
            "last_name": "",
            "company": null,
            "phone": null,
            "sign_up_mode": "GOOGLE_PLUS"
        }
    }
}

Above format is common . Only data key can hold different types of information like user, product, invoice etc.

I want to keep status, message and data keys same in every rest response. data will be treated according to status and message will be displayed to user.

So basically, above format is desired in all apis. Only information inside data key will be different each time.

And I've setup a following class and set it up as gson converter - MyResponse.java

public class MyResponse<T> implements Serializable{
    private boolean status ;
    private String message ;
    private T data;

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

Deserializer.java

class Deserializer<T> implements JsonDeserializer<T>{
    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException{
        JsonElement content = je.getAsJsonObject();

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion to this deserializer
        return new Gson().fromJson(content, type);

    }
}

And used it as follows -

GsonBuilder  gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); 
gsonBuilder.registerTypeAdapter(MyResponse.class, new Deserializer<MyResponse>());
...... ..... ....

restBuilder.setConverter(new GsonConverter(gsonBuilder.create()));

Service interface is as follows -

@POST("/register")
public void test1(@Body MeUser meUser, Callback<MyResponse<MeUser>> apiResponseCallback);


@POST("/other")
public void test2(Callback<MyResponse<Product>> apiResponseCallback);

Problem

I can access status and message fields from inside callback. But information inside data key is not parsed and model like MeUser and Product always returns as empty.

If I change json structure to following above code works perfectly -

{
        "status": true,
        "message": "Registration Complete.",
        "data": {                
                "username": "user88",
                "email": "[email protected]",
                "created_on": "1426171225",
                "last_login": null,
                "active": "1",
                "first_name": "User",
                "last_name": "",
                "company": null,
                "phone": null,
                "sign_up_mode": "GOOGLE_PLUS"
        }
    }

How can I have it worked with specifying separate key inside data object and parse it successfully ?

like image 468
Gimali Avatar asked Mar 13 '15 07:03

Gimali


2 Answers

If I can suggest to change something in json is that you have to add at one new field that defines the type of data, so json should look like below:

{
   "status": true,
   "message": "Registration Complete.",
   "dataType" : "user",
   "data": {                
            "username": "user88",
            "email": "[email protected]",
            "created_on": "1426171225",
            "last_login": null,
            "active": "1",
            "first_name": "User",
            "last_name": "",
            "company": null,
            "phone": null,
            "sign_up_mode": "GOOGLE_PLUS"
    }
}

The MyResponse class has to have new filed DataType so it should look like below:

public class MyResponse<T> implements Serializable{
    private boolean status ;
    private String message ;
    private DataType dataType ;
    private T data;


    public DataType getDataType() {
        return dataType;
    }

    //... other getters and setters
}

The DataType is an enum which defines type of data. You have to pass Data.class as param in constructor. For all data types you have to create new classes. DataType enum should look like below:

public enum DataType {

    @SerializedName("user")
    USER(MeUser.class),
    @SerializedName("product")
    Product(Product.class),
    //other types in the same way, the important think is that 
    //the SerializedName value should be the same as dataType value from json 
    ;


    Type type;

    DataType(Type type) {
        this.type = type;
    }

    public Type getType(){
        return type;
    }
}

The desarializator for Json should looks like below:

public class DeserializerJson implements JsonDeserializer<MyResponse> {

    @Override
    public MyResponse deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        JsonObject content = je.getAsJsonObject();
        MyResponse message = new Gson().fromJson(je, type);
        JsonElement data = content.get("data");
        message.setData(new Gson().fromJson(data, message.getDataType().getType()));
        return message;

    }
}

And when you create RestAdapter, in the line where you register Deserializator, you should use this :

 .registerTypeAdapter(MyResponse.class, new DeserializerJson())

Other classes (types of data) you define like standard POJO for Gson in separated classes.

like image 99
Konrad Krakowiak Avatar answered Nov 18 '22 11:11

Konrad Krakowiak


Your issue is because the data attribute is defined as T which you expect to be of types MeUser, Product, etc, but is actually of an object which has inner attribute like user. To resolve this, you need to introduce another level of classes which has the required attributes user, product, invoice etc. This can be easily achieved using static inner classes.

public class MeUser{
  private User user;
  public static class User{
    private String username;
    //add other attributes of the User class
  }
}
like image 35
Rajesh Avatar answered Nov 18 '22 11:11

Rajesh