Serialize and deserialize a list of polymorphic objects with GSON

GSON is a great Java library for serializing and deserializing objects to and from JSON strings. When you have a list of polymorphic objects you want to serialize and deserialize, things get a bit more difficult. You have to tell GSON which type of object you expect when deserializing. When you have a list of polymorphic objects, the only thing you can tell GSON is the superclass (parent), but this prevents GSON from creating objects with specific subclass (child) properties.

One elegant solution to this problem is the use of the RuntimeTypeAdapterFactory class. This class is not part of the GSON distribution, but it is developed as an extra which you can download from the GSON GitHub site.

RuntimeTypeAdapterFactory requires a field in your superclass that distinguishes its subclasses. It uses this field to create a specific subclass object when you deserialize the JSON string. Let’s use the example of a superclass called Animal:

public class Animal {
    protected String name;
    protected String type;

    public Animal(String name, String type) {
        this.name = name;
        this.type = type;
    }

    @Override
    public String toString() {
        return "Animal [name=" + name + ", type=" + type + "]";
    }
}

Now we create two subclasses called Dog and Cat:

public class Dog extends Animal {
    private boolean playsCatch;

    public Dog(String name, boolean playsCatch) {
        super(name, "dog");
        this.playsCatch = playsCatch;
    }

    @Override
    public String toString() {
        return "Dog [name=" + name + ", type=" + type + ", playsCatch=" + playsCatch + "]";
    }
}

public class Cat extends Animal {
    private boolean chasesLaser;

    public Cat(String name, boolean chasesLaser) {
        super(name, "cat");
        this.chasesLaser = chasesLaser;
    }

    @Override
    public String toString() {
        return "Cat [name=" + name + ", type=" + type + ", chasesLaser=" + chasesLaser + "]";
    }
}

Now for the actual serialization and deserialization:

List<Animal> animals = new ArrayList<>();
animals.add(new Dog("dog1", true));
animals.add(new Dog("dog2", false));
animals.add(new Cat("cat1", false));

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
    .of(Animal.class, "type")
    .registerSubtype(Dog.class, "dog")
    .registerSubtype(Cat.class, "cat");
Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create();

String json = gson.toJson(animals);
Type listType = new TypeToken<List<Animal>>(){}.getType();
List<Animal> fromJson = gson.fromJson(json, listType);
for (Animal animal : fromJson) {
    if (animal instanceof Dog) {
        System.out.println(animal + " DOG");
    } else if (animal instanceof Cat) {
        System.out.println(animal + " CAT");
    } else if (animal instanceof Animal) {
        System.out.println(animal + " ANIMAL");
    } else {
        System.out.println("Class not found");
    }
}

The important part here is the creation of the RuntimeTypeAdapterFactory. In this case, you tell GSON to look at the “type” field to distinguish between Cat and Dog in the case of an Animal. The output looks like this:

Dog [name=dog1, type=null, playsCatch=true] DOG
Dog [name=dog2, type=null, playsCatch=false] DOG
Cat [name=cat1, type=null, chasesLaser=false] CAT

GSON now successfully created specific Dog and Cat objects instead of Animal objects. It didn’t fill the “type” field to its correct value, but that will only hurt you if you would serialize these objects again.

3 thoughts on “Serialize and deserialize a list of polymorphic objects with GSON

  1. Permalink  ⋅ Reply

    Ferenc Hechler

    February 25, 2017 at 7:07pm

    Great solution for handling polymorphism in a clean and transparent way.
    In fact, the field “type” is not needed in the classes. Its a “virutal” discriminator field for the deserialization.

  2. Permalink  ⋅ Reply

    Umakanth

    March 25, 2017 at 10:32pm

    Thanks for the post. With respect to the last line “It didn’t fill the “type” field to its correct value”, I have other fields in my base class the value for which is available in the input JSON input file but the object is not loaded with it. Is that expected? Anyways to get the values for those fields?

  3. Permalink  ⋅ Reply

    Vlad

    April 13, 2017 at 8:41am

    What if i try to serialise again? I need it because i store these objects in shared preferences. How would i work around that?

Leave a Reply

Your email will not be published. Name and Email fields are required.