Michał Kowol Tech Blog

How to deserialize abstract types from JSON using Jackson?

Overview

Sometimes you need deserialize JSON to Java abstract types. Good example is list of animals.

{
  "animals": [
    {
      "name": "Szarik",
      "ageInYears": 3,
      "type": "dog"
    },
    {
      "name": "Filemon",
      "livesLeft": 6,
      "type": "cat"
    }
  ]
}

In this case you need to serialize animals to specific types using type property.

Java Data Classes (Lombock)

import lombok.Data;

interface Animal {
}

@Data
class AnimalsContainer {
    private final List<Animal> animals = new LinkedList<>();
}

@Data
class Dog implements Animal {
    private final String name;
    private final Integer ageInYears;
}

@Data
class Cat implements Animal {
    private final String name;
    private final Integer livesLeft;
}

Json Type Info Annotations

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = Dog.class, name = "dog"),
        @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
interface Animal {
}

Please note that we use property type to determine specific subclass of Animal.

Test (jUnit 5)

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class AnimalsTest {
    @Test
    @DisplayName("it should deserialize arrays of animal to specific types")
    public void animals() throws Throwable {
        // given
        String json = Files.readAsText("mediabus/examples/animals.json");
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.findAndRegisterModules();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // when
        List<Animal> animals = objectMapper.readValue(json, AnimalsContainer.class).getAnimals();
        // then
        assertEquals(2, animals.size());
        assertEquals(Dog.class, animals.get(0).getClass());
        assertEquals(3, ((Dog) animals.get(0)).getAgeInYears().intValue());
        assertEquals(Cat.class, animals.get(1).getClass());
        assertEquals(6, ((Cat) animals.get(1)).getLivesLeft().intValue());
    }
}

System.out.println(animals); prints [Dog(name=Szarik, ageInYears=3), Cat(name=Filemon, livesLeft=6)].