3

Is there a simple way to deserialize a value that may be either a String or a List<String>?

I have to process a large JSON that I'll abbreviate like:

{"A": {"item1": 1,
        ...
       "itemN": "SomeString",
        ...
       "itemM": 123.45},
 "B": { ... },
 "C": { ... },
 }

And sometimes it looks like this:

{"A": {"item1": 1,
        ...
       "itemN": ["SomeString1", "SomeString2"],
        ...
       "itemM": 123.45},
 "B": { ... },
 "C": { ... },
 }

I deserialize with:

MyData data = new Gson().fromJson(rxJSON, DataClass.class);

Where DataClass:

@Parcel
public class DataClass {
    @SerializedName("A")
    private AClass groupA;
    @SerializedName("B")
    private BClass groupB;
    @SerializedName("C")
    private BClass groupC;
    ... getters/setters ...
}

and AClass:

@Parcel
public class AClass {
    @SerializedName("item1")
    private int item1;
    ...
    @SerializedName("itemN")
    private List<String> itemN;
    ...
    @SerializedName("itemM")
    private float itemM;
    ... getters/setters ...
}

Ideally, I'd like to use AClass for both JSONs, and in the case where itemN is a String, simply treat it as if it were a single element List<String> (ie. treat "SomeString" as ["SomeString"]). After deserializing, I always want to access it as a list for simplicity in the rest of my code.

I've seen suggestions for Polymophism solutions and solutions suggesting attempting to deserialize with one version of a class assuming one type (such as String) and in an exception catch deserialize with a version of the class assuming the other type (such as List<String>). Other solutions suggest a more manual/piece-wise deserialization where it would deserialize only one level of the JSON hierarchy at a time until I came to itemN and then check it's type. It seems like there should be a simpler way. Is there?

2
  • Possible duplicate: stackoverflow.com/q/18993414/18157 Commented Dec 9, 2016 at 6:39
  • @JimGarrison I don't see how to apply the link you suggest without parsing the JSON in layers which is what I'm trying to avoid. Am I missing something? Commented Dec 9, 2016 at 15:59

1 Answer 1

1

I found a good solution thanks to: https://stackoverflow.com/a/31563539/3571110 and https://stackoverflow.com/a/6205384/3571110

The first link shows using a custom deserializer for the entire JSON (too much work for me), and within that manages String to List<String>. The second link gave me the insight that I could make a custom deserializer for basic types like List<> (I previously thought I could only use custom classes). Combining those ideas and making the appropriate changes yields (everything else staying the same):

Solution:

public class ListOrStringDeserializer implements JsonDeserializer<List<String>> {

    @Override
    public List<String> deserialize(JsonElement json, Type typeOfT,
                                    JsonDeserializationContext context) 
            throws JsonParseException {

        List<String> items = Collections.emptyList();
        if (json.isJsonArray()) {
            items = context.deserialize(json, List.class);
        } else if (json.isJsonPrimitive()) {
            items = Collections.singletonList((String) context.deserialize(json, String.class));
        }
        return items;
    }
}

Then register before deserializing:

Gson gson = new GsonBuilder().registerTypeAdapter(new TypeToken<List<String>>() {}.getType(), new ListOrStringDeserializer()).create();
MyData data = gson.fromJson(rxJSON, DataClass.class);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.