Canoe
Canoe

Reputation: 599

Build an instance of Gson that can inflate subclasses

I have a list of objects that I need to deserialize into their proper subclasses. I have been able to get this working by building a gson object like below

    JsonDeserializer<Shape> jdTypes = (json, typeOfT, jsonContext) -> {
        JsonObject jsonObject = json.getAsJsonObject();
        int type = jsonObject.get("type").getAsInt();

        Gson gson = new Gson();

        switch (type){
            case Types.TYPE_CARD:
                return gson.fromJson(json, Card.class);

            case Types.TYPE_BLOCK:
                return gson.fromJson(json, Block.class);

            case Types.TYPE_STRIPE:
                return gson.fromJson(json, Stripe.class);

            //...

        }
    };

    GsonBuilder builder = new GsonBuilder();
    builder.registerTypeAdapter(Shape.class, jdTypes);
    builder.create();

Although it is working, there has to be a better way of doing it. Currently I am creating an instance of Gson within my GsonBuilder. Yikes. Is there a way I can use the Gson object I am about to build to deserialize the different sub-types?

Upvotes: 0

Views: 200

Answers (2)

Lyubomyr Shaydariv
Lyubomyr Shaydariv

Reputation: 21145

No need to reinvent the wheel. Your problem is classic in the scope of Gson, and there is a special RuntimeTypeAdapterFactory that is designed to solve problems of exactly such a kind. For example, if you have something like this:

final class Types {

    private Types() {
    }

    static final int TYPE_CARD = 1;
    static final int TYPE_BLOCK = 2;
    static final int TYPE_STRIPE = 3;

    abstract static class Abstract {

        private Abstract() { }

        static final class Card extends Abstract { }    
        static final class Block extends Abstract { }   
        static final class Stripe extends Abstract {}

    }

}

You can configure and use RuntimeTypeAdapterFactory:

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(
                RuntimeTypeAdapterFactory.of(Types.Abstract.class, "type")
                        .registerSubtype(Types.Abstract.Card.class, String.valueOf(Types.TYPE_CARD))
                        .registerSubtype(Types.Abstract.Block.class, String.valueOf(Types.TYPE_BLOCK))
                        .registerSubtype(Types.Abstract.Stripe.class, String.valueOf(Types.TYPE_STRIPE))
        )
        .create();

Then you don't even need anything else:

private static final Type abstractListType = new TypeToken<List<Types.Abstract>>() {
}.getType();

public static void main(final String... args) {
    gson.<List<Types.Abstract>>fromJson("[{\"type\":1},{\"type\":2},{\"type\":3}]", abstractListType)
            .stream()
            .map(Types.Abstract::getClass)
            .forEach(System.out::println);
}

Output:

class q43159721.Types$Abstract$Card
class q43159721.Types$Abstract$Block
class q43159721.Types$Abstract$Stripe

Upvotes: 1

tynn
tynn

Reputation: 39873

If I understand this issue correctly, you could just use the JsonDeserializationContext directly.

return jsonContext.deserialize(json, classOfType(type));

Where classOfType() should just return the the class corresponding to the type of your shape.

Upvotes: 1

Related Questions