Jackson Json is a powerful Java library to serialize and deserialize objects to/from Json.
You may ask yourself:
- How can I serialize and deserialize polymorphic class instances?
- How to configure Jackson to serialize objects being represented by their interface?
Good news! The answer is just below.
It’s somehow difficult to find real examples showing how to do this. That’s why I’ve decided to make this little tutorial to help you get the idea quickly with practical code examples.
Polymorphism
Polymorphism is the ability to have different implementations represented by a single interface or abstract class. This article describes how to serialize and deserialize objects by their interface, as well as Polymorphic Tree Structured object instances.
Please note this example is written in Java 8 and uses Lombok. Lombok has numerous benefits like generating getters, toString, equals and hashcode methods.
Dependencies
First, let’s add the Maven dependencies to our pom.xml
:
1
2
3
4
5
6
7
|
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.3</version>
</dependency>
|
It’s recommended to always use the latest version. Check Jackson Databind on Maven Central.
Example
Let’s take the following example:
1
2
3
4
5
|
public interface Vehicle {
String getName();
}
|
It has a Car
implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import static java.util.Objects.requireNonNull;
@Value
public class Car implements Vehicle {
String name;
@JsonCreator
public Car(@JsonProperty("name") final String name) {
this.name = requireNonNull(name);
}
}
|
And a Truck
implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import static java.util.Objects.requireNonNull;
@Value
public class Truck implements Vehicle {
String name;
@JsonCreator
public Truck(@JsonProperty("name") final String name) {
this.name = requireNonNull(name);
}
}
|
Implementations
The Vehicle
interface has two implementations: Car
and Truck
. Now let’s suppose we want to serialize the following object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import java.util.List;
import static java.util.Objects.requireNonNull;
@Value
public class Vehicles {
List<Vehicle> vehicles;
@JsonCreator
public Vehicles(@JsonProperty("vehicles") final List<Vehicle> vehicles) {
super();
this.vehicles = requireNonNull(vehicles);
}
}
|
In order to be able to serialize / deserialize the Vehicles
instance, Jackson Json needs to know how to instantiate a Vehicle
instance. It needs to know whenever it’s a Truck
or a Car
instance. In order to do this, Jackson Json must be configured. There are several ways to do so.
Direct mapping
In the following example, the mapping is directly configured on the Vehicle
interface:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME;
@JsonTypeInfo(use = NAME, include = PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value=Truck.class, name = "Truck"),
@JsonSubTypes.Type(value=Car.class, name = "Car")
})
public interface Vehicle {
String getName();
}
|
This solution works well when the interface and the implementation are placed within the same package. However, it introduces a cyclic dependency between the Vehicle
interface and its implementations.
Type mapping
The other solution is the configure the type mapping separately on the ObjectMapper
. Let’s see how in this JUnit test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class VehicleTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
MAPPER.registerSubtypes(new NamedType(Truck.class, "Truck"));
MAPPER.registerSubtypes(new NamedType(Car.class, "Car"));
}
@Test
public void shouldSerializeVehicles() throws IOException {
final Vehicles vehicles = new Vehicles(ImmutableList.of(new Car("Dodge"), new Truck("Scania")));
final String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(vehicles);
final Vehicles read = MAPPER.readValue(json, Vehicles.class);
assertEquals(vehicles, read);
}
}
|
This JUnit does the following:
- It configures the type mapping in a
static
block. A Truck
instance is mapped to the named type Truck for example,
- And It runs a JUnit which checks the serialization / deserialization produces exactly the same object.
If we take a look at the produced Json, it looks like the following:
1
2
3
4
5
6
7
8
9
10
11
|
{
"vehicles" : [ {
"@type" : "Car",
"name" : "Dodge"
}, {
"@type" : "Truck",
"name" : "Scania"
} ]
}
|
Jackson has added a @type
attribute to each vehicle json. This special attribute is used to identify the type of vehicle being serialized. Jackson then uses this information during deserialization to create the right class instance.
Polymorphic tree structure
Serialization
Great! Now that we’ve covered a basic example, let’s see how to serialize a polymorphic tree structure. Here is an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import java.util.List;
@Value
public class CarTransporter implements Vehicle {
String name;
List<Vehicle> vehicles;
@JsonCreator
public CarTransporter(
@JsonProperty("name") final String name,
@JsonProperty("vehicles") final List<Vehicle> vehicles) {
super();
this.name = requireNonNull(name);
this.vehicles = requireNonNull(vehicles);
}
}
|
A CarTransporter
is a Vehicle
itself! But, it’s also able to carry Vehicle
instances. In fact, it could technically carry another CarTransporter
although a real one wouldn’t be able too… It’s not the point here.
Unit test
Now let’s include another unit-test to try this out:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class VehicleTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
MAPPER.registerSubtypes(new NamedType(Truck.class, "Truck"));
MAPPER.registerSubtypes(new NamedType(Car.class, "Car"));
MAPPER.registerSubtypes(new NamedType(CarTransporter.class, "CarTransporter"));
}
@Test
public void shouldSerializeCarTransporter() throws IOException {
final Vehicle transporter = new CarTransporter("Transporter", ImmutableList.of(new Car("Dodge"), new Truck("Scania")));
final String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(transporter);
final Vehicle read = MAPPER.readValue(json, Vehicle.class);
assertEquals(transporter, read);
}
}
|
Json
As you can see, I’ve added the CarTransporter
type mapping. The unit-test above serializes and deserializes a CarTransporter
instance. The produced json looks like the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{
"@type" : "CarTransporter",
"name" : "Transporter",
"vehicles" : [ {
"@type" : "Car",
"name" : "Dodge"
}, {
"@type" : "Truck",
"name" : "Scania"
} ]
}
|
I hope you see now that there is nothing difficult here! Jackson can serialize and deserialize polymorphic data structures very easily. The CarTransporter
can itself carry another CarTransporter
as a vehicle: that’s where the tree structure is!
Now, you know how to configure Jackson to serialize and deserialize objects being represented by their interface.
Hi Jérôme
Thanks for your article, it was helpful. Question: How do you get the vehicles list to print without the list type info, i.e. I want: “vehicles” : [ { rather than “vehicles” : [ “java.util.ArrayList”, [ { which is what I have. It used to work out of the box until I introduced polymorphism via JsonTypeInfo
Any pointers? Anna
In reply to Anna Joffe
Hi Anna, Sorry but I have never seen this before. We’re using
@JsonTypeInfo
at OctoPerf and never encountered that issue here. Make sure you don’t have put this annotation in the wrong place. It should be on the interface implemented by all implementations.In reply to Anna Joffe
Hi Anna, I think you’ve enabled below “mapper.enableDefaultTyping()”
I cannot get Type Mapping as in the Unit Test example to work. I have an abstract base class that I cannot annotate or change. Several classes inherit from the base class. I would like to serialise a list of class instances that inherit from the base class. I have created a wrapper class that contains a list. I create a MAPPER in a JUnit class as you have done. But the Type information is not printed. Any ideas?
In reply to Joan Killeen
Hi Joan, What you ask is exactly covered by the article:
Hello! Nice tutorial. Is there any way you could disable the @type property on the json output?
In reply to Andreas Alme
Hi Andreas,
It’s not possible since
@type
property within the json output let Jackson know which class must be instantiated.