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.
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.
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?
What if i try to serialise again? I need it because i store these objects in shared preferences. How would i work around that?
What to do if Animal is abstract?
The class RuntimeTypeAdapterFactory not found in library of Gson! Is She deprecated?
thanks.
It doesn’t come with original Gson library. You’d have to use an additional library: GsonExtras.
Pingback:Deserializing a generic type using gson in kotlin - JTuto