Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write GSON custom deserializer for embedded object with two possible types

I am having trouble trying to deserialize a java object because the field ("info") inside the object can be one of two possible types: either ArrayList or just a String.Here is what I did so far:

First, create class Base:

public class Base {
}

Next create subclasses:

public class GoodInfo extends Base {
    public ArrayList<MyCustomObject> info;
}

public class BadInfo extends Base {
    public String info;
}

So now I would like to parse my JSON which is an ArrayList of Base objects (i.e. an ArrayList of objects where each object is either an ArrayList or a String):

Type listOfBase = new TypeToken<ArrayList<Base>>(){}.getType();
ArrayList<Base> resp=gson.fromJson(jsonText, listOfBase);

I know that for this to work, I must write a custom deserializer. The deserializer looks like this:

private class MyCustomDeserializer implements JsonDeserializer<DateTime> {
    public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
  throws JsonParseException {
        // WHAT DO I DO HERE?
    }
}

As you can see, I don't know what to do to try to deserialize each of these subtypes and return the type that works. Anyone know how to do this?

I'm thinking it would look something like this:

private class MyCustomDeserializer implements JsonDeserializer<DateTime> {
    public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
  throws JsonParseException {
        try {
            GoodInfo goodInfo=SOMEHOW TRY TO DESERIALIZE json INTO A GoodInfo object
            return goodInfo;
        } catch {
            //
        }
        try {
            BadInfo badInfo=SOMEHOW TRY TO DESERIALIZE json INTO A BadInfo object
            return badInfo;
        } catch {
            throw new JsonParseException("Could not deserialize");
        }
    }
}

I cannot use context.deserialize on json passed in, according to GSON: Invokes default deserialization on the specified object. It should never be invoked on the element received as a parameter of the JsonDeserializer.deserialize(JsonElement, Type, JsonDeserializationContext) method. Doing so will result in an infinite loop since Gson will in-turn call the custom deserializer again.

So how do I do this?

like image 950
Marc Avatar asked Dec 23 '14 00:12

Marc


1 Answers

No the documentation reads (emphasis mine):

...you should never invoke it on the same type passing json since that will cause an infinite loop...

It's perfectly fine to invoke context.deserialize(...) as long as the type is different.

Rather than catching exceptions in your deserializer, you can use json to inspect the info field and take the appropriate action based on the element type, for example:

public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
    final JsonElement elem = json.getAsJsonObject()
                                 .get("info");
    if (elem.isJsonArray()) {
        return context.deserialize(json, GoodInfo.class);
    }
    return context.deserialize(json, BadInfo.class);
}

Alternatively you can bypass a custom JsonDeserializer altogether by modifying the superclass. Pulling-up the info field as an Object, e.g.:

public class Base {
    public Object info;
}

Will allow Gson to deserialize the value appropriately.

like image 194
Jonathan Avatar answered Sep 24 '22 04:09

Jonathan