Handling polymorphism with Jackson

Sometime you need to serialize a hierarchical model to JSON. Jackson offers different ways to handle the problem. Here I will expose two of them.

tl;dr;

First solution: Annotate the super class with @JsonTypeInfo(use=Id.CLASS)Pros: works with any subtype. Cons: breaks with classes or package renaming.

Second solution: Annotate the super class with:

@JsonTypeInfo(use=Id.NAME)
@JsonSubTypes({
		@JsonSubTypes.Type(value=Cat.class, name="Cat"),
		@JsonSubTypes.Type(value=Dog.class, name="Dog")
})

adding a JsonSubTypes.Type for each sub-class. Pros: no problems with renaming if custom names are provided. Cons: requires to specify all the sub-classes in the super-class.

Long story

We have animals:

public abstract class Animal {
 public String name;
}

between them dogs:
public class Dog extends Animal {

 public double barkVolume;

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

and cats:
public class Cat extends Animal {
 
 public boolean likesCream;
 public int lives;
 
 @Override
 public String toString() {
 return "Cat [name=" + name + ", likesCream=" + likesCream + ", lives=" + lives + "]";
 }
}

We try to serialize a cat:
ObjectMapper mapper = new ObjectMapper();
		
Cat cat = new Cat();
cat.name = "Fuffy";
cat.likesCream = false;
cat.lives = 7;
		
String json = mapper.writeValueAsString(cat);
		
System.out.println(json);

obtaining the following JSON:
{
  "name" : "Fuffy",
  "likesCream" : false,
  "lives" : 7
}

But when we try to deserialize it:
String json = ...

Animal expectedCat = mapper.readValue(json, Animal.class);
System.out.println(expectedCat);

we get an exception:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.github.fedy2.jhe.model.Animal: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: {"name":"Fuffy","likesCream":false,"lives":7}; line: 1, column: 1]
	at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
	at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:149)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
	at com.github.fedy2.jhe.SerializeDeserialize.main(SerializeDeserialize.java:28)

Not good. Looks like Jackson is not able to understand if the JSON refers to a cat or to a dog.

We can help Jackson annotating the super-class with the JsonTypeInfo annotation. The annotation will add type information to the generated JSON.

We can tell Jackson to use the fully-qualified Java class name as type information:

@JsonTypeInfo(use=Id.CLASS)
public abstract class Animal {
	...
}

a @class property will be added with the full class name:
{
  "@class" : "com.github.fedy2.jhe.model.Cat",
  "name" : "Fuffy",
  "likesCream" : false,
  "lives" : 7
}

If you like a shorter class name you can use the Id.MINIMAL_CLASS option:
@JsonTypeInfo(use=Id.MINIMAL_CLASS)
public abstract class Animal {
 ...
}

a @c property will be added with a shorter class name:
{
  "@c" : ".Cat",
  "name" : "Fuffy",
  "likesCream" : false,
  "lives" : 7
}

With both solutions we should be worried about refactoring: if we change the classes or package names we will not be able to deserialized previously stored JSON.

As alternative we can store custom names using the Id.NAME option:

@JsonTypeInfo(use=Id.NAME)
public abstract class Animal {
 ...
}

Obtaining a JSON with a new property @type with the type name:
{
  "@type" : "Cat",
  "name" : "Fuffy",
  "likesCream" : false,
  "lives" : 7
}

By default Jackson uses the class name as name.

Unfortunately during the deserialization we get an exception:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'Cat' into a subtype of [simple type, class com.github.fedy2.jhe.model.Animal]: known type ids = [Animal]
 at [Source: {"@type":"Cat","name":"Fuffy","likesCream":false,"lives":7}; line: 1, column: 10]
	at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:42)
	at com.fasterxml.jackson.databind.DeserializationContext.unknownTypeIdException(DeserializationContext.java:1477)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownTypeId(DeserializationContext.java:1170)
	at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleUnknownTypeId(TypeDeserializerBase.java:282)
	at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._findDeserializer(TypeDeserializerBase.java:156)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:112)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:142)
	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:63)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
	at com.github.fedy2.jhe.SerializeDeserialize.main(SerializeDeserialize.java:28)

Jackson is not able to map the type name to a class. To solve the problem we provide sub-class information with the JsonSubTypes annotation:
@JsonTypeInfo(use=Id.NAME)
@JsonSubTypes({
		@JsonSubTypes.Type(value=Cat.class),
		@JsonSubTypes.Type(value=Dog.class)
})
public abstract class Animal {
 ...
}

We can use custom names specifying them in the Type annotation:
@JsonTypeInfo(use=Id.NAME)
@JsonSubTypes({
		@JsonSubTypes.Type(value=Cat.class, name="Gatto"),
		@JsonSubTypes.Type(value=Dog.class, name="Cane")
})
public abstract class Animal {
 ...
}

Now we get a JSON with our custom names:
{
  "@type" : "Gatto",
  "name" : "Fuffy",
  "likesCream" : false,
  "lives" : 7
}

We can obtain the same result annotating the sub-class with a JsonTypeName annotation:
@JsonTypeName("Gatto")
public class Cat extends Animal {
 ...
}

With custom names we can refactoring without problems but we will need to specify all the subtypes in the super class.

For more information please refer to official documentation: http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

Released yahoo-weather-java-api 1.0

This is an old library that I’ve started in 2011 in order to play with JAXB, Git, Maven and Github.

The library is a wrapper of the Yahoo Weather API. It is lightweight and requires only slf4j.

In this version there are no particular changes. The javadoc has been updated and the package names and the group id has been adapted to the maven central rules. Then the library has been released in maven central.

You can get the library using this dependency coordinates:

<dependency>
    <groupId>com.github.fedy2</groupId>
    <artifactId>yahoo-weather-java-api</artifactId>
    <version>1.0.0</version>
</dependency>

The library is hosted here: https://github.com/fedy2/yahoo-weather-java-api