Akshay Goyal
Akshay Goyal

Reputation: 951

handle multiple JSON Objects types in a JSON Array using Retrofit2

My response from the server is a list of different types of JSON objects, which depends on each responses' "type". I have different beans for each response type. Can Retrofit use appropriate Bean class for each response (JSON Object)in the list(JSON Array)?

   {
    "cards": [2]
        0:  {
            "type": "A"
            "ID": 24
            "author_name": "ari"
            "title": "BB"
            "permalink": ""
            "created_date": "2015-12-18 17:17:00"
            "post_date": "Dec 18 2015"
            "thumbnail": ""
            "summary": "Stars"
            "thumbSmall": "100.jpg"
            "androidXL": "500.jpg"
        }-
        1:  {
            "type": "B"
            "_id": "3290"
            "read_count": 0
            "categories": [1]
                0:  "abc"      
            "title": "New"
            "isSticky": false
            "section": [0]
            "author": "zzz"
            "india": false
            "update_time": 1450415789694
            "summary": "Social"
            "scoreId": "nz"
            "isActive": true
            "image": ""
            "timestamp": 1450385165210
            "events": [1]
                0:  "nz"
            "teams": [0]
            "slug": "new"
            "isDeleted": false
            "score_str": "SL"
        }
    }

As a workaround, I have created a new Bean class, with all possible fields, almost half the fields are null for each JSON Object.
Is there a better way of doing this?

Upvotes: 0

Views: 1545

Answers (1)

Singed
Singed

Reputation: 1113

You can create a super class which contains data common for all types and extend it:

public abstract class SuperItem {

 public enum Type {
    @SerializedName("A")
    TYPEA(1),

    @SerializedName("B")
    TYPEB(2);

    Type(final int value) {
        this.value = value;
    }

public static Type fromInteger(int i) {
        switch (i) {
            case 1:
                return TYPEA;
            case 2:
                return TYPEB;
            default:
                Log.d("SuperItem", "No existing type for integer: " + i);
                return null;
        }
    }
}

}

@SerializedName("type") private Type type;
}

public class AItem extends SuperItem {

}
public class BItem extends SuperItem {

}

Retrofit by default relies on GSON for JSON deserialization. You will need to create custom Gson deserializer for your implementation and set it to your RestAdapter:

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(SuperItem.class, new SuperItemDeserializer());
Gson gson = builder.build();
 Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build();

Your custom deserializer can look like this:

public class SuperItemDeserializer implements     JsonDeserializer<SuperItem> {

@Override
public SuperItem deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
    Gson gson = new Gson();
    SuperItem item = null;
    JsonObject rootObject = json.getAsJsonObject();
    int typeInt = rootObject.get("type").getAsInt();
    SuperItem.Type type = SuperItem.Type.fromInteger(typeInt);
    switch (type) {
        case TYPEA:
            item = gson.fromJson(json, AItem.class);
            break;
        case TYPEB:
            item = gson.fromJson(json, BItem.class);
            break;
        default:
            Log.d("TAG", "Invalid type: " + typeInt);
    }
    return item;
}
}

Hope the naming in the example is not too bad and you'll get the idea :)

Upvotes: 2

Related Questions