Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error java java

Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error


As promised, I'm putting an example for how to use annotations to serialize/deserialize polymorphic objects, I based this example in the Animal class from the tutorial you were reading.

First of all your Animal class with the Json Annotations for the subclasses.

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;import com.fasterxml.jackson.annotation.JsonSubTypes;import com.fasterxml.jackson.annotation.JsonTypeInfo;@JsonIgnoreProperties(ignoreUnknown = true)@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)@JsonSubTypes({    @JsonSubTypes.Type(value = Dog.class, name = "Dog"),    @JsonSubTypes.Type(value = Cat.class, name = "Cat") })public abstract class Animal {    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

Then your subclasses, Dog and Cat.

public class Dog extends Animal {    private String breed;    public Dog() {    }    public Dog(String name, String breed) {        setName(name);        setBreed(breed);    }    public String getBreed() {        return breed;    }    public void setBreed(String breed) {        this.breed = breed;    }}public class Cat extends Animal {    public String getFavoriteToy() {        return favoriteToy;    }    public Cat() {}    public Cat(String name, String favoriteToy) {        setName(name);        setFavoriteToy(favoriteToy);    }    public void setFavoriteToy(String favoriteToy) {        this.favoriteToy = favoriteToy;    }    private String favoriteToy;}

As you can see, there is nothing special for Cat and Dog, the only one that know about them is the abstract class Animal, so when deserializing, you'll target to Animal and the ObjectMapper will return the actual instance as you can see in the following test:

public class Test {    public static void main(String[] args) {        ObjectMapper objectMapper = new ObjectMapper();        Animal myDog = new Dog("ruffus","english shepherd");        Animal myCat = new Cat("goya", "mice");        try {            String dogJson = objectMapper.writeValueAsString(myDog);            System.out.println(dogJson);            Animal deserializedDog = objectMapper.readValue(dogJson, Animal.class);            System.out.println("Deserialized dogJson Class: " + deserializedDog.getClass().getSimpleName());            String catJson = objectMapper.writeValueAsString(myCat);            Animal deseriliazedCat = objectMapper.readValue(catJson, Animal.class);            System.out.println("Deserialized catJson Class: " + deseriliazedCat.getClass().getSimpleName());        } catch (Exception e) {            e.printStackTrace();        }    }}

Output after running the Test class:

{"@type":"Dog","name":"ruffus","breed":"english shepherd"}

Deserialized dogJson Class: Dog

{"@type":"Cat","name":"goya","favoriteToy":"mice"}

Deserialized catJson Class: Cat

Hope this helps,

Jose Luis


You need only one line before the declaration of the class Animal for correct polymorphic serialization/deserialization:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")public abstract class Animal {   ...}

This line means: add a meta-property on serialization or read a meta-property on deserialization (include = JsonTypeInfo.As.PROPERTY) called "@class" (property = "@class") that holds the fully-qualified Java class name (use = JsonTypeInfo.Id.CLASS).

So, if you create a JSON directly (without serialization) remember to add the meta-property "@class" with the desired class name for correct deserialization.

More information here


Whereas @jbarrueta answer is perfect, in the 2.12 version of Jackson was introduced a new long-awaited type for the @JsonTypeInfo annotation, DEDUCTION.

It is useful for the cases when you have no way to change the incoming json or must not do so. I'd still recommend to use use = JsonTypeInfo.Id.NAME, as the new way may throw an exception in complex cases when it has no way to determine which subtype to use.

Now you can simply write

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;import com.fasterxml.jackson.annotation.JsonSubTypes;import com.fasterxml.jackson.annotation.JsonTypeInfo;@JsonIgnoreProperties(ignoreUnknown = true)@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)@JsonSubTypes({    @JsonSubTypes.Type(Dog.class),    @JsonSubTypes.Type(Cat.class) })public abstract class Animal {    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

And it will produce {"name":"ruffus", "breed":"english shepherd"} and {"name":"goya", "favoriteToy":"mice"}

Once again, it's safer to use NAME if some of the fields may be not present, like breed or favoriteToy.