Java/Json Polymorphic serialization & De-Serialization using Jackson.

Jackson has a number of flexible ways to map Java objects to and from JSON.Serialization or de-serializing a java object is straight forward using Jackson.But when you have a Interface or Abstract class which has multiple implementations then serializing & De-Serializing  using Jackson is little tricky and this is also called Polymorphic serialization & De-Serialization.

Here in this post, I focus on serialization and deserialization of polymorphic types.

Polymorphic deserialization using de-serializer :


If Interface or abstract class have multiple implementations and try to deserialize any implementation with Interface then you will find below error

Can not construct instance of com.MyInterface, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information

To overcome this error, You could write a custom deserializer for Interface or abstract class, which would have to programmatically parse the JSON depending on the fields and parse the body as whatever concrete class you had in mind.

Let me explain by taking simple pojos

@JsonDeserialize(using = AnimalDeSerializer.class)
public interface Animal {
}

@JsonDeserialize(as = Lion.class)
public class Lion implements Animal {

    private String name;
    private String roar;

//constructor & setters & getters
}

@JsonDeserialize(as = Tiger.class)
public class Tiger implements Animal {

    private String name;
    private String purr;
//constructor & setters & getters
}


AnimalDeSerializer :


public class AnimalDeSerializer extends JsonDeserializer<Animal> {

    public static final String NAME = "name";
    public static final String LION = "lion";
    public static final String TIGER = "tiger";

    @Override
    public Animal deserialize(JsonParser jp, DeserializationContext context) throws IOException {

        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        ObjectNode root = mapper.readTree(jp);
        if (root.has(NAME)) {
            JsonNode jsonNode = root.get(NAME);
            if(jsonNode.asText().equals(LION)) {
                return mapper.readValue(root.toString(), Lion.class);
            }else if(jsonNode.asText().equals(TIGER))
            {
                return mapper.readValue(root.toString(), Tiger.class);
            }
        }
        throw context.mappingException("Failed to de-serialize animal, as name property not exist");
    }
}


Demo :
String animalString = "{\"name\":\"tiger\",\"purr\":\"purr\"}";
ObjectMapper objectMapper = new ObjectMapper();
Animal animal = objectMapper.readValue(animalString, Animal.class);
System.out.println(animal);

You should annotate Instance with @JsonDeserialize(using = AnimalDeSerializer.class) to indicate the class to be used to deserialize the Interface or abstract class.

Note: You may get Stackoverflow error if you doesn't annotate implementation class.All implementation class should deserialize with themselves, else it will use the parent class deserializer which leads to StackOverflowError.

Polymorphic serialization & deserialization using @JsonTypeInfo and @JsonSubTypes annotations :


I focus on serialization and deserialization of polymorphic types using @JsonTypeInfo and @JsonSubTypes which maintain sub type information while serializing java object and recreate the exact sub type.

In the example animal is a Interface and it can be an tiger or a lion, and they both extend the Animal Interface . While deserializing we want to create the exact animal type and demonstrate the use of @JsonTypeInfo and @JsonSubTypes annotations.


@JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
        include=JsonTypeInfo.As.PROPERTY,
        property="name")
@JsonSubTypes({
        @JsonSubTypes.Type(value=Lion.class, name="lion"),
        @JsonSubTypes.Type(value=Tiger.class, name="tiger"),
})
public interface Animal {

}

@JsonTypeName("lion")
public class Lion implements Animal {

    private String name;
    private String roar;

//constructor & setters & getters
}

@JsonTypeName("tiger")
public class Tiger implements Animal {

    private String name;
    private String purr;

//constructor & setters & getters
}


Demo :

List<Animal> animal = new ArrayList<Animal>();
animal.add(new Lion("lion", "roar"));
animal.add(new Tiger("tiger", "purr"));
animal.add(new Tiger("tiger", "purrrrrrrrr"));

URL url = JacksonPolymorphicSerialization.class.getClassLoader().getResource("animals.json");
File file = new File(url.getPath());

// de-serailization sub types
new ObjectMapper().writeValue(file, animal);

// serailization animals and its subtype
List<Animal> animals = new ObjectMapper().readValue(file, List.class);
System.out.println(animals);


output : [{name=lion, roar=roar}, {name=tiger, purr=purr}, {name=tiger, purr=purrrrrrrr}]

Hope this helps you understanding serializing and deserializing polymorphic types using Jackson.

Show Comments: OR

2 comments:

Unknown said...

Wonderful article, very helpful!

Ana Paula said...

Thanks, man!!