34

I asked this in a different question today but I'm afraid that won't get any solution because of how it was phrased.

I have a json input that has the following data:

Json

As you can see, the option_value item is an Array in one object and a simple string in another object.

How can I make Gson handle this properly? My class has this described as a List object, so it works for the first few items where option_value is an array, but when it becomes a string, the app crashes and I get a json parse exception.

Is there a workaround for this?

UPDATE

Adding the relevant part of my class as requested:

public class Options
    {
        String product_option_id;
        String option_id;
        String name;
        String type;
        String required;
        List<OptionValue> option_value;

        // get set stuff here

        public class OptionValue
        {
            String product_option_value_id;
            String option_value_id;
            String name;
            String image;
            String price;
            String price_prefix;

            // get set stuff here
        }
    }
Asim
  • 6,962
  • 8
  • 38
  • 61
  • Hi Asim, can you show your Json Parser ? – Kansen Feb 04 '15 at 10:54
  • Added the relevant part. I'm using Gson so the parsing is done by that library using the Gson.fromJson(string, class) function. – Asim Feb 04 '15 at 10:57
  • You can not achieve this using `GSON.fromJson(String json, Class classOfT)`, you will have to define your adapter... lets say `StrangeJsonAdapter` by extending `com.google.gson.TypeAdapter` class... then use `StrangeJsonAdapter.fromJson(String json)` method to decode your json. – sarveshseri Feb 04 '15 at 11:01
  • It was working fine before I started parsing this part of the json (option_value). What makes you think the above json can't be parsed using fromJson? – Asim Feb 04 '15 at 11:03
  • Please, mark Denis answer as answer :) – IliaEremin Feb 09 '16 at 11:50
  • Done. Although, I think I used some other method to deal with it. But his is also a very good (and correct) answer. – Asim Feb 09 '16 at 17:53

2 Answers2

32

I have a solution for you :) For this purpose, we should use a custom deserializer. Remake your class like this:

    public class Options{

        @SerializedName ("product_option_id");
        String mProductOptionId;

        @SerializedName ("option_id");
        String mOptionId;

        @SerializedName ("name");
        String mName;

        @SerializedName ("type");
        String mType;

        @SerializedName ("required");
        String mRequired;

        //don't assign any serialized name, this field will be parsed manually
        List<OptionValue> mOptionValue;

        //setter
        public void setOptionValues(List<OptionValue> optionValues){
             mOptionValue = optionValues;
        }

        // get set stuff here
        public class OptionValue
        {
            String product_option_value_id;
            String option_value_id;
            String name;
            String image;
            String price;
            String price_prefix;

            // get set stuff here
        }

    public static class OptionsDeserializer implements JsonDeserializer<Options> {

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

            if (jsonObject.has("option_value")) {
                JsonElement elem = jsonObject.get("option_value");
                if (elem != null && !elem.isJsonNull()) {  
                     String valuesString = elem.getAsString();
                     if (!TextUtils.isEmpty(valuesString)){
                         List<OptionValue> values = new Gson().fromJson(valuesString, new TypeToken<ArrayList<OptionValue>>() {}.getType());
                         options.setOptionValues(values);
                     }
                }
            }
            return options ;
        }
    }
    }

Before we can let Gson parse json, we should register our custom deserializer:

    Gson gson = new GsonBuilder()              
                .registerTypeAdapter(Options.class, new Options.OptionsDeserilizer())               
                .create();

And now - just call:

    Options options = gson.fromJson(json, Options.class);
Negar Zamiri
  • 641
  • 2
  • 11
  • 16
Denys Vasylenko
  • 2,135
  • 18
  • 20
  • 4
    I got a StackOverflowError due to the recursive deserialization call on itself at `Options options = new Gson().fromJson(json, Options.class);` and two new `Gson` instances are created in the deserialization of a single object. Look out. – EpicPandaForce Jan 18 '16 at 11:19
  • 1
    @EpicPandaForce is there a way to prevent this StackOverflow? – tamtom Jun 06 '19 at 17:02
  • 1
    @tamtom I'm not sure. It's been 3.5 years and I don't remember this question, nor my comment. I don't remember what exactly I ran into and when. – EpicPandaForce Jun 06 '19 at 23:11
6

In my situation, the field with same name is "data":{} or "data":[array_with_real_data]. So the code from accepted answer need to be modified slightly, like this:

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

    if (jsonObject.has("data")) {
        JsonArray array = jsonObject.getAsJsonArray("data");
        if (array != null && !array.isJsonNull()) {
            List<Data> data = new Gson().fromJson(array, new TypeToken<ArrayList<Data>>() {}.getType());
            bean.realData = data;
        }
    }
    return bean ;
}

hope that helps.

StoneLam
  • 370
  • 6
  • 11