Jackson JSON: deserialize a list of objects of subclasses of an abstract class

In this post we see how to serialize and deserialize in JSON a Java class that declares an instance variable consisting in a list of objects of an abstract class that contains objects of its various concrete subclasses.
We start by creating our abstract class:

public abstract class MyItem {

    private int id;
    private String name;

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public MyItem(int id, String name) {
        this.id = id;
        this.name = name;
    }

}

We continue creating 3 different concrete subclasses of MyItem abstract class:

public class MySubItemA extends MyItem {

    private String itemAProperty1;
    private String itemAProperty2;

    public String getItemAProperty1() {
        return this.itemAProperty1;
    }

    public void setItemAProperty1(String itemAProperty1) {
        this.itemAProperty1 = itemAProperty1;
    }

    public String getItemAProperty2() {
        return this.itemAProperty2;
    }

    public void setItemAProperty2(String itemAProperty2) {
        this.itemAProperty2 = itemAProperty2;
    }


    public MySubItemA(int id, String name, String p1, String p2) {
        super(id, name);
        this.itemAProperty1 = p1;
        this.itemAProperty2 = p2;
    }
}
public class MySubItemB extends MyItem {

    private int itemBProperty1;
    private String itemBProperty2;

    public int getItemBProperty1() {
        return this.itemBProperty1;
    }

    public void setItemBProperty1(int itemBProperty1) {
        this.itemBProperty1 = itemBProperty1;
    }

    public String getItemBProperty2() {
        return this.itemBProperty2;
    }

    public void setItemBProperty2(String itemBProperty2) {
        this.itemBProperty2 = itemBProperty2;
    }

    public MySubItemB(int id, String name, int p1, String p2) {
        super(id, name);
        this.itemBProperty1 = p1;
        this.itemBProperty2 = p2;
    }
}
public class MySubItemC extends MyItem {

    private int itemCProperty1;
    private int itemCProperty2;

    public int getItemCProperty1() {
        return this.itemCProperty1;
    }

    public void setItemCProperty1(int itemCProperty1) {
        this.itemCProperty1 = itemCProperty1;
    }

    public int getItemCProperty2() {
        return this.itemCProperty2;
    }

    public void setItemCProperty2(int itemCProperty2) {
        this.itemCProperty2 = itemCProperty2;
    }

    public MySubItemC(int id, String name, int p1, int p2) {
        super(id, name);
        this.itemCProperty1 = p1;
        this.itemCProperty2 = p2;
    }
}

Finally, we create a client class that contains a list of objects of the abstract class

import java.util.ArrayList;
import java.util.List;

public class ClientObject {

    private List<MyItem> l;

    public ClientObject(List<MyItem> pl) {
        this.l = pl;
    }

    public ClientObject() {
        this.l = new ArrayList<MyItem>();
    }

    public List<MyItem> getL() {
        return this.l;
    }

    public void setL(List<MyItem> l) {
        this.l = l;
    }
}

At this point we create a test class where we instantiate some concrete subclasses objects, add them to the list of MyItem that’s the instance member of ClientObject class and we then try to serialize the whole ClientObject object.

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class TestJSONSubclassing {

    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();

        ClientObject c = new ClientObject();

        MyItem i1 = new MySubItemA(1, "Value1", "Some stuff", "Another property value");
        MyItem i2 = new MySubItemB(2, "Value2", 1000, "B property");
        MyItem i3 = new MySubItemC(3, "Value3", 2000, -1);
        MyItem i4 = new MySubItemA(4, "Value4", "Bla Bla Bla", "item A property");

        c.getL().add(i1);
        c.getL().add(i2);
        c.getL().add(i3);
        c.getL().add(i4);

        String s = null;
        try {
            s = mapper.writeValueAsString(c);
        }
        catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(s);

    }
}

Running the program we get the following output:

{"l":[{"id":1,"name":"Value1","itemAProperty1":"Some stuff","itemAProperty2":"Another property value"},{"id":2,"name":"Value2","itemBProperty1":1000,"itemBProperty2":"B property"},{"id":3,"name":"Value3","itemCProperty1":2000,"itemCProperty2":-1},{"id":4,"name":"Value4","itemAProperty1":"Bla Bla Bla","itemAProperty2":"item A property"}]}

We can ask to our Jackson ObjectMapper object to use the indented format option in order to make the JSON string more readable:

mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
{
  "l" : [ {
    "id" : 1,
    "name" : "Value1",
    "itemAProperty1" : "Some stuff",
    "itemAProperty2" : "Another property value"
  }, {
    "id" : 2,
    "name" : "Value2",
    "itemBProperty1" : 1000,
    "itemBProperty2" : "B property"
  }, {
    "id" : 3,
    "name" : "Value3",
    "itemCProperty1" : 2000,
    "itemCProperty2" : -1
  }, {
    "id" : 4,
    "name" : "Value4",
    "itemAProperty1" : "Bla Bla Bla",
    "itemAProperty2" : "item A property"
  } ]
}

As we can see elements of the MyItem objects list have been properly serialized as JSON string, with all properties with the right value. What is missing however is the information, for each object, about his real type, that is, of which of different concrete subclasses it is an instance. Without this information, deserialization of the JSON representation in a list of objects of concrete subclasses is not possible, because Jackson may not know the actual objects type.

Let’s see what happens performing ClientObject object deserialization. We modify the code of our test class as follows, adding the instruction for deserializing the JSON string in a new ClientObject instance and a loop for printing the objects contained in its list of MyItem:

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class TestJSONSubclassing {

    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.INDENT_OUTPUT, true);

        ClientObject c = new ClientObject();

        MyItem i1 = new MySubItemA(1, "Value1", "Some stuff", "Another property value");
        MyItem i2 = new MySubItemB(2, "Value2", 1000, "B property");
        MyItem i3 = new MySubItemC(3, "Value3", 2000, -1);
        MyItem i4 = new MySubItemA(4, "Value4", "Bla Bla Bla", "item A property");

        c.getL().add(i1);
        c.getL().add(i2);
        c.getL().add(i3);
        c.getL().add(i4);

        String s = null;
        try {
            s = mapper.writeValueAsString(c);
        }
        catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // System.out.println(s);

        ClientObject c2 = null;
        try {
            c2 = mapper.readValue(s, ClientObject.class);
        }
        catch (JsonParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (JsonMappingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        if (c2 != null) {
            System.out.println("----- Items List -----");

            for (MyItem mi : c2.getL())
                System.out.println("Type = " + mi.getClass() +  ", id = "+ mi.getId() + ", name = " + mi.getName());
        }
    }
}

Running the program we get the following result:

com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of net.davismol.jsonsubclassing.MyItem, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: {
  "l" : [ {
    "id" : 1,
    "name" : "Value1",
    "itemAProperty1" : "Some stuff",
    "itemAProperty2" : "Another property value"
  }, {
    "id" : 2,
    "name" : "Value2",
    "itemBProperty1" : 1000,
    "itemBProperty2" : "B property"
  }, {
    "id" : 3,
    "name" : "Value3",
    "itemCProperty1" : 2000,
    "itemCProperty2" : -1
  }, {
    "id" : 4,
    "name" : "Value4",
    "itemAProperty1" : "Bla Bla Bla",
    "itemAProperty2" : "item A property"
  } ]
}; line: 2, column: 11] (through reference chain: net.davismol.jsonsubclassing.ClientObject["l"]->java.util.ArrayList[0])
	at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
	at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:771)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:140)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:232)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:206)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:25)
	at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:538)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:99)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:238)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:118)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3051)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2146)
	at net.davismol.jsonsubclassing.TestJSONSubclassing.main(TestJSONSubclassing.java:42)

As we can see, and as we predicted, an exception is thrown. It indicates that we can not instantiate objects of an abstract class, and then we have to map these objects against concrete classes, or to create a dedicated deserializer. The solution we choose to solve the problem is the first one, that is, we define in the MyItem class which are concrete subclasses that inherit from it and we add this information within JSON string generated by the serialization so that later, during deserialization, it would be possible to instantiate an object of the right concrete type.

To do this we use some annotations provided by Jackson: @JsonTypeInfo , @JsonSubTypes and @Type

  • @JsonTypeInfo: it is used to indicate to add during serialization a new property for the type of the object and to define its name
  • @JsonSubTypes: it allows to define which are conrete subclasses of the annotated class
  • @Type: it indicates actual subclasses and allows to give them a “name”, if necessary

We can see a first example, by changing the MyItem class code as follows:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

@JsonTypeInfo(use = Id.CLASS,
              include = JsonTypeInfo.As.PROPERTY,
              property = "type")
@JsonSubTypes({
    @Type(value = MySubItemA.class),
    @Type(value = MySubItemB.class),
    @Type(value = MySubItemC.class),
    })
public abstract class MyItem {

    private int id;
    private String name;

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public MyItem(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

The result obtained in this case is as follows, where we can see that in the JSON string for each item in the list of MyItem, a new property called “type” (specified by the annotation attribute property = “type”) has been added and it has been fill with the full name of the concrete subclass the object was an instance of (specified by the annotation attribute use = Id.CLASS). In this way, during deserialization we have the information about the concrete class to be used to instantiate each element.

{
  "l" : [ {
    "type" : "net.davismol.jsonsubclassing.MySubItemA",
    "id" : 1,
    "name" : "Value1",
    "itemAProperty1" : "Some stuff",
    "itemAProperty2" : "Another property value"
  }, {
    "type" : "net.davismol.jsonsubclassing.MySubItemB",
    "id" : 2,
    "name" : "Value2",
    "itemBProperty1" : 1000,
    "itemBProperty2" : "B property"
  }, {
    "type" : "net.davismol.jsonsubclassing.MySubItemC",
    "id" : 3,
    "name" : "Value3",
    "itemCProperty1" : 2000,
    "itemCProperty2" : -1
  }, {
    "type" : "net.davismol.jsonsubclassing.MySubItemA",
    "id" : 4,
    "name" : "Value4",
    "itemAProperty1" : "Bla Bla Bla",
    "itemAProperty2" : "item A property"
  } ]
}

If we modify the value of the annotation “use” attribute from Id.CLASS to Id.NAME (Id is an enumerator) we get the “type” property set with only the class name instead of the full name containing also its package:

@JsonTypeInfo(use = Id.NAME,
              include = JsonTypeInfo.As.PROPERTY,
              property = "type")
{
  "l" : [ {
    "type" : "MySubItemA",
    "id" : 1,
    "name" : "Value1",
    "itemAProperty1" : "Some stuff",
    "itemAProperty2" : "Another property value"
  }, {
    "type" : "MySubItemB",
    "id" : 2,
    "name" : "Value2",
    "itemBProperty1" : 1000,
    "itemBProperty2" : "B property"
  }, {
    "type" : "MySubItemC",
    "id" : 3,
    "name" : "Value3",
    "itemCProperty1" : 2000,
    "itemCProperty2" : -1
  }, {
    "type" : "MySubItemA",
    "id" : 4,
    "name" : "Value4",
    "itemAProperty1" : "Bla Bla Bla",
    "itemAProperty2" : "item A property"
  } ]
}

If you want you can also redefine the “names” of the concrete subclasses to be used as “type” property values in the JSON string, using the “name” attribute of @Type annotation. We can see an example, by changing the MyItem class annotations as follows:

@JsonTypeInfo(use = Id.NAME,
              include = JsonTypeInfo.As.PROPERTY,
              property = "type")
@JsonSubTypes({
    @Type(value = MySubItemA.class, name = "subA"),
    @Type(value = MySubItemB.class, name = "subB"),
    @Type(value = MySubItemC.class, name = "subC"),
    })

In this case we get as serialization result the following string:

{
  "l" : [ {
    "type" : "subA",
    "id" : 1,
    "name" : "Value1",
    "itemAProperty1" : "Some stuff",
    "itemAProperty2" : "Another property value"
  }, {
    "type" : "subB",
    "id" : 2,
    "name" : "Value2",
    "itemBProperty1" : 1000,
    "itemBProperty2" : "B property"
  }, {
    "type" : "subC",
    "id" : 3,
    "name" : "Value3",
    "itemCProperty1" : 2000,
    "itemCProperty2" : -1
  }, {
    "type" : "subA",
    "id" : 4,
    "name" : "Value4",
    "itemAProperty1" : "Bla Bla Bla",
    "itemAProperty2" : "item A property"
  } ]
}

Now that we’ve seen how to include in the JSON serialization string information about the actual objects type, let’s see if deserialization works properly. But first we have to make one more change to our concrete subclasses, indicating which is the constructor to be used, through the @JsonCreator annotation, and how to map the JSON properties to its input parameters, through the @JsonProperty annotation.

The MySubItemA class therefore has to be changed as follows and, in the same way, also MySubItemB and MySubItemC classes.

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class MySubItemA extends MyItem {

    private String itemAProperty1;
    private String itemAProperty2;

    public String getItemAProperty1() {
        return this.itemAProperty1;
    }

    public void setItemAProperty1(String itemAProperty1) {
        this.itemAProperty1 = itemAProperty1;
    }

    public String getItemAProperty2() {
        return this.itemAProperty2;
    }

    public void setItemAProperty2(String itemAProperty2) {
        this.itemAProperty2 = itemAProperty2;
    }

    @JsonCreator
    public MySubItemA(@JsonProperty("id")int id, @JsonProperty("name")String name, @JsonProperty("itemAProperty1")String p1, @JsonProperty("itemAProperty2")String p2) {
        super(id, name);
        this.itemAProperty1 = p1;
        this.itemAProperty2 = p2;
    }

}

We can modify our test class in order to perform both serialization and deserialization of the ClientObject object.

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class TestJSONSubclassing {

    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.INDENT_OUTPUT, true);

        ClientObject c = new ClientObject();

        MyItem i1 = new MySubItemA(1, "Value1", "Some stuff", "Another property value");
        MyItem i2 = new MySubItemB(2, "Value2", 1000, "B property");
        MyItem i3 = new MySubItemC(3, "Value3", 2000, -1);
        MyItem i4 = new MySubItemA(4, "Value4", "Bla Bla Bla", "item A property");

        c.getL().add(i1);
        c.getL().add(i2);
        c.getL().add(i3);
        c.getL().add(i4);

        String s = null;
        try {
            s = mapper.writeValueAsString(c);
        }
        catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(s);

        ClientObject c2 = null;
        try {
            c2 = mapper.readValue(s, ClientObject.class);
        }
        catch (JsonParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (JsonMappingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        if (c2 != null) {
            System.out.println("----- Items List -----");

            for (MyItem mi : c2.getL()) {
                System.out.println("Type = " + mi.getClass() +  ", id = "+ mi.getId() + ", name = " + mi.getName());
            }
        }
    }
}

Running again the program we get the following result:

{
  "l" : [ {
    "type" : "subA",
    "id" : 1,
    "name" : "Value1",
    "itemAProperty1" : "Some stuff",
    "itemAProperty2" : "Another property value"
  }, {
    "type" : "subB",
    "id" : 2,
    "name" : "Value2",
    "itemBProperty1" : 1000,
    "itemBProperty2" : "B property"
  }, {
    "type" : "subC",
    "id" : 3,
    "name" : "Value3",
    "itemCProperty1" : 2000,
    "itemCProperty2" : -1
  }, {
    "type" : "subA",
    "id" : 4,
    "name" : "Value4",
    "itemAProperty1" : "Bla Bla Bla",
    "itemAProperty2" : "item A property"
  } ]
}
----- Items List -----
Type = class net.davismol.jsonsubclassing.MySubItemA, id = 1, name = Value1
Type = class net.davismol.jsonsubclassing.MySubItemB, id = 2, name = Value2
Type = class net.davismol.jsonsubclassing.MySubItemC, id = 3, name = Value3
Type = class net.davismol.jsonsubclassing.MySubItemA, id = 4, name = Value4

As we can see, this time deserialization is successful and the list of MyItem objects has been created by instantiating various objects of the right concrete subclasses.

All classes used in this post example can be downloaded here:

14 thoughts on “Jackson JSON: deserialize a list of objects of subclasses of an abstract class

  1. Hi,
    thanks for this tutorial. I have a similar problem. I have an abstract class, which has lets say two sub-classes. For reasons of simplicity I’ll call the abstract class “Animal” and the subclasses “Cat” and “Dog”.

    Now I followed your tutorial, but the type of the subclass is not automatically written to the JSON-file.
    I tried with mapper.writeFor(Animal.class) and they are written then. But I can only return one element instead of a list.

    • Same issue, i am trying to get the child class instance but im only getting parsing of parent class attributes, i don’t want to force it by using hardcoded implementation of the parent class in the “mapper.readValue(s, ClientObject.class)” to get a child instance from json (in this case reading json)

  2. class A {
    int id;
    String name;
    }

    class B {
    String property1;
    String property2;
    String property3;
    }

    A extends B

    Response:
    {
    property1: ‘value1’,
    property2: ‘value2’,
    property3: ‘value3’,
    id:1,
    name: ‘my name’
    }

    I want to display as flow
    {
    id:1,
    name: ‘my name’,
    property1: ‘value1’,
    property2: ‘value2’,
    property3: ‘value3’

    }
    How to do that?

  3. Pingback: Jackson JSON: usare l’annotazione JsonPropertyOrder per definire l’ordine di serializzazione delle proprietà | Dede Blog

  4. Pingback: Jackson JSON: using @JsonPropertyOrder annotation to define properties serialization order | Dede Blog

  5. Nice tutorial!

    But is it not concerning that annotations on parent class use information about children?
    If I create MySubItemD, I would have to modify MyItem to add the class in annotations.
    It would not be possible to do so if I am not in control of the parent source code.

Leave a Reply

Your email address will not be published. Required fields are marked *