0

I am fetching data from API provided by our Backend team. One of the keys data sometimes contains JSONObject and sometimes it contains JSONArray. I am using GSON to parse the response and it is throwing an exception because of this.

I tried the solution given in this thread but it's not working in my case. How to dynamically handle json response array/object using Gson

enter image description here

POJO (EntityDetailResponseV2):

package com.tf.eros.faythTv.objects.entityDetail;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import com.tf.eros.faythTv.objects.entityFeatured.EntityFeaturedResponse;
import com.tf.eros.faythTv.utils.ArrayAdapterFactory;

import java.util.List;

public class EntityDetailResponseV2 {

    @SerializedName("results")
    private Results results;

    @SerializedName("success")
    private boolean success;

    public static EntityDetailResponseV2 getObject(String res) {

        try {
            Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ArrayAdapterFactory()).create();
            return gson.fromJson(res, EntityDetailResponseV2.class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public Results getResults() {
        return results;
    }

    public boolean isSuccess() {
        return success;
    }

    public static class Results {

        @SerializedName("id")
        Integer entityID;

        @SerializedName("name")
        String entityName;

        @SerializedName("description")
        String entityDescription;

        @SerializedName("image_url")
        String entityImageURL;

        @SerializedName("square_entity_url")
        String entitySquareURL;

        @SerializedName("slug")
        String entitySlug;

        @SerializedName("live")
        boolean isEntityLive;

        @SerializedName("twitter_username")
        String twitterUserName;

        @SerializedName("fb_username")
        String fbUserName;

        @SerializedName("featured")
        private List<EntityFeaturedResponse> featured;

        @SerializedName("collections")
        List<Collections> collectionsList;

        public Integer getEntityID() {
            return entityID;
        }

        public String getEntityName() {
            return entityName;
        }

        public String getEntityDescription() {
            return entityDescription;
        }

        public String getEntityImageURL() {
            return entityImageURL;
        }

        public String getEntitySquareURL() {
            return entitySquareURL;
        }

        public String getEntitySlug() {
            return entitySlug;
        }

        public boolean isEntityLive() {
            return isEntityLive;
        }

        public String getTwitterUserName() {
            return twitterUserName;
        }

        public String getFbUserName() {
            return fbUserName;
        }

        public List<EntityFeaturedResponse> getFeatured() {
            return featured;
        }

        public List<Collections> getCollectionsList() {
            return collectionsList;
        }

        public static class Collections {

            @SerializedName("type")
            String type;

            @SerializedName("type_id")
            Integer typeID;

            //Data sometimes contains JSON object and sometimes it contains JSON Array
            @SerializedName("data")
            List<Data> data;

            public String getType() {
                return type;
            }

            public Integer getTypeID() {
                return typeID;
            }

            public List<Data> getData() {
                return data;
            }

            public static class Data {

                @SerializedName("view_all")
                boolean viewAll;

                @SerializedName("title")
                String title;

                public boolean isViewAll() {
                    return viewAll;
                }

                public String getTitle() {
                    return title;
                }
            }
        }
    }

}

ArrayAdapterFactory.java

public class ArrayAdapterFactory implements TypeAdapterFactory {

    @SuppressWarnings({"unchecked"})
    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {

        ArrayAdapter typeAdapter = null;
        try {
            if (type.getRawType() == List.class) {

                typeAdapter = new ArrayAdapter(
                        (Class) ((ParameterizedType) type.getType())
                                .getActualTypeArguments()[0]);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return typeAdapter;
    }

    public class ArrayAdapter<T> extends TypeAdapter<List<T>> {
        private Class<T> adapterClass;

        public ArrayAdapter(Class<T> adapterClass) {

            this.adapterClass = adapterClass;
        }

        public List<T> read(JsonReader reader) throws IOException {


            List<T> list = new ArrayList<T>();

            Gson gson = new Gson();

            if (reader.peek() == JsonToken.BEGIN_OBJECT) {

                T inning = (T) gson.fromJson(reader, adapterClass);
                list.add(inning);

            } else if (reader.peek() == JsonToken.BEGIN_ARRAY) {

                reader.beginArray();
                while (reader.hasNext()) {
                    //read(reader);
                    T inning = (T) gson.fromJson(reader, adapterClass);
                    list.add(inning);
                }
                reader.endArray();

            } else {
                reader.skipValue();
            }

            return list;
        }

        public void write(JsonWriter writer, List<T> value) throws IOException {

        }

    }
}

As complete API response is very large, I am providing a link to .json file. https://drive.google.com/open?id=1RMOiM7UjOwR-5b0Ik7ymy65ZOhc8I8hY

UPDATE: I tried the solution which is mentioned below but still, DataType1 and DataType2 both are coming nulls. Although I am no longer getting the GSON exception.

public class EntityDetailResponseV2 {

    @SerializedName("results")
    private Results results;

    @SerializedName("success")
    private boolean success;

    public static EntityDetailResponseV2 getObject(String res) {

        try {

            Gson gson = new GsonBuilder().registerTypeAdapter(Collections.class, new Results.Collections.CollectionItemDeserializer()).create();
            return gson.fromJson(res, EntityDetailResponseV2.class);

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public Results getResults() {
        return results;
    }

    public boolean isSuccess() {
        return success;
    }

    public static class Results {

        @SerializedName("id")
        Integer entityID;

        @SerializedName("name")
        String entityName;

        @SerializedName("description")
        String entityDescription;

        @SerializedName("image_url")
        String entityImageURL;

        @SerializedName("square_entity_url")
        String entitySquareURL;

        @SerializedName("slug")
        String entitySlug;

        @SerializedName("live")
        boolean isEntityLive;

        @SerializedName("twitter_username")
        String twitterUserName;

        @SerializedName("fb_username")
        String fbUserName;

        @SerializedName("featured")
        private List<EntityFeaturedResponse> featured;

        @SerializedName("collections")
        List<Collections> collectionsList;

        public Integer getEntityID() {
            return entityID;
        }

        public String getEntityName() {
            return entityName;
        }

        public String getEntityDescription() {
            return entityDescription;
        }

        public String getEntityImageURL() {
            return entityImageURL;
        }

        public String getEntitySquareURL() {
            return entitySquareURL;
        }

        public String getEntitySlug() {
            return entitySlug;
        }

        public boolean isEntityLive() {
            return isEntityLive;
        }

        public String getTwitterUserName() {
            return twitterUserName;
        }

        public String getFbUserName() {
            return fbUserName;
        }

        public List<EntityFeaturedResponse> getFeatured() {
            return featured;
        }

        public List<Collections> getCollectionsList() {
            return collectionsList;
        }


        public static class Collections {

            @SerializedName("type")
            String type;

            @SerializedName("order")
            Integer order;

            @SerializedName("type_id")
            Integer typeId;

            DataType1 dataType1;
            List<DataType2> dataType2;

            public String getType() {
                return type;
            }

            public Integer getOrder() {
                return order;
            }

            public Integer getTypeId() {
                return typeId;
            }

            public DataType1 getDataType1() {
                return dataType1;
            }

            public List<DataType2> getDataType2() {
                return dataType2;
            }

            public static class CollectionItemDeserializer implements JsonDeserializer<Collections> {

                @Override
                public Collections deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                    Collections collectionItem = new Gson().fromJson(json, Collections.class);
                    JsonObject jsonObject = json.getAsJsonObject();

                    if (collectionItem.getType() != null) {

                        JsonElement element = jsonObject.get("data");
                        switch (collectionItem.getTypeId()) {
                            case AppConstants.ENTITY_SHOP:
                            case AppConstants.ENTITY_MEDIA_COLLECTION:
                            case AppConstants.ENTITY_ECOM_COLLECTION:
                            case AppConstants.ENTITY_BANNER:

                                collectionItem.dataType1 = new Gson().fromJson(element, DataType1.class);
                                break;

                            case AppConstants.ENTITY_TOP_AUDIOS:
                            case AppConstants.ENTITY_TOP_VIDEOS:
                            case AppConstants.ENTITY_LATEST_VIDEOS:
                            case AppConstants.ENTITY_TOP_PLAYLISTS:
                            case AppConstants.ENTITY_WALLPAPERS:
                            case AppConstants.ENTITY_QUOTATIONS:

                                List<DataType2> values = new Gson().fromJson(element, new TypeToken<ArrayList<DataType2>>() {}.getType());
                                collectionItem.dataType2 = values;
                                break;
                        }
                    }
                    return collectionItem;
                }
            }

            public static class DataType1 {
                @SerializedName("view_all")
                boolean viewAll;

                public boolean isViewAll() {
                    return viewAll;
                }
            }

            public static class DataType2 {
                @SerializedName("view_all")
                boolean viewAll;

                public boolean isViewAll() {
                    return viewAll;
                }
            }
        }

    }

}

enter image description here

Maddy
  • 4,525
  • 4
  • 33
  • 53
Shikhar Deep
  • 253
  • 2
  • 8
  • 19

1 Answers1

1

You need to use a custom JsonDeserializer. Here's the solution :

public class CollectionListItem {

    @SerializedName ("type")
    String type;

    @SerializedName ("order")
    Integer order;

    @SerializedName ("id")
    Integer id;

    @SerializedName ("type_id")
    Integer typeId;

    DataType1 dataType1;
    List<DataType2> dataType2;
    List<DataType3> dataType3;

    final static String DATA_TYPE_1 = "SHOP";
    final static String DATA_TYPE_2 = "TOP_AUDIOS";
    final static String DATA_TYPE_3 = "MEDIA_COLLECTION";

    //Public getters and setters

    public static class CollectionItemDeserializer implements JsonDeserializer<CollectionListItem> {

        @Override
        public CollectionListItem deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            CollectionListItem collectionItem = new Gson().fromJson(json, CollectionListItem.class);
            JsonObject jsonObject = json.getAsJsonObject();

            if (collectionItem.getType() != null) {

                JsonElement element = jsonObject.get("data");
                switch(collectionItem.getType()){
                    case CollectionListItem.DATA_TYPE_1 :
                        collectionItem.dataType1 = new Gson().fromJson(element, DataType1.class);
                        break;

                    case CollectionListItem.DATA_TYPE_2:
                        List<DataType2> values = new Gson().fromJson(element, new TypeToken<ArrayList<DataType2>>() {}.getType());
                        collectionItem.dataType2 = values;
                        break;

                    case CollectionListItem.DATA_TYPE_3:
                        List<DataType3> values_ = new Gson().fromJson(element, new TypeToken<ArrayList<DataType3>>() {}.getType());
                        collectionItem.dataType3 = values_;
                        break;
                }
            }
            return collectionItem;
        }
    }

}

DataType1, DataType2, etc are the individual classes for different responses.

Also, add this line to register your custom deserializer with gson

Gson gson = new GsonBuilder()
            .registerTypeAdapter(CollectionListItem.class,
                    new CollectionListItem.CollectionItemDeserializer())
            .create();
vepzfe
  • 4,217
  • 5
  • 26
  • 46
  • I tried your solution. I am no longer getting exception but DataType1, DataType2 both are coming nulls. Actually, I just need two DataType (as "data" can be either JSON Object or JSON Array) and I can differentiate it inside switch() using collectionItem.getTypeID (instead of collectionItem.getType()). I added "view_all" boolean to both DataType1, DataType2 but both are mapping to null (even though the first response in "collections" (with type: "SHOP" has "view_all": false inside "data" key). I have added the new code in question description – Shikhar Deep Dec 01 '17 at 06:53
  • According to the JSON you've provided, the "data" key is referring to more than 2 different data types. Lets say first type is Pojo1, and then there's List and List so you need to write the conditions accordingly. Don't just think of as JsonObject and JsonArray, but JsonObject and JsonArray of Type1, Type2, Type3. – vepzfe Dec 01 '17 at 08:02
  • yeah, I think it will be good to have separate POJOs for different types like you have mentioned. But, why DataType1, DataType2 both are coming nulls. The first response in "collections" (with type: "SHOP" has "view_all": false inside "data" key). I have added the new code to question description – Shikhar Deep Dec 03 '17 at 10:01
  • You need to use Boolean instead of boolean! – vepzfe Dec 05 '17 at 12:58
  • Also, best not use inner static classes like you are. They are not meant for usecases like this. Read this : https://stackoverflow.com/a/7486111/3090120 – vepzfe Dec 05 '17 at 13:00