jsonserializationjacksonobject-identity

Jackson combining @JsonIdentityInfo and @JsonTypeInfo throws InvalidTypeIdException


Currently I am having an issue with Jackson when I combine @JsonIdentityInfo and @JsonTypeInfo. The Kotlin code below throws an exception on the last line. It serializes the dog1AndDog1Json instance as expected into Json but it then throws an exception while deserializing it back into an instance.

package some.test

import com.fasterxml.jackson.annotation.*
import com.fasterxml.jackson.databind.ObjectMapper

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonSubTypes(JsonSubTypes.Type(value = Dog::class), JsonSubTypes.Type(value = Cat::class))
interface Animal {
    val name: String
}

@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator::class)
data class Dog(@JsonProperty("name") override val name: String) : Animal

@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator::class)
data class Cat(@JsonProperty("name") override val name: String) : Animal

data class TwoAnimals(@JsonProperty("animal1") val animal1: Animal, @JsonProperty("animal2") val animal2: Animal)

fun main() {
    val om = ObjectMapper();

    val dog1 = Dog("Dog1")
    val dog2 = Dog("Dog2")
    val cat1 = Cat("Cat1")

    val dog1AndDog2 = TwoAnimals(dog1, dog2)
    val dog1AndDog2Json = om.writerWithDefaultPrettyPrinter().writeValueAsString(dog1AndDog2)
    assert(dog1AndDog2 === om.readValue(dog1AndDog2Json, TwoAnimals::class.java)) // OK

    val dog1AndCat1 = TwoAnimals(dog1, cat1)
    val dog1AndCat2Json = om.writerWithDefaultPrettyPrinter().writeValueAsString(dog1AndCat1)
    assert(dog1AndCat1 === om.readValue(dog1AndCat2Json, TwoAnimals::class.java)) // OK

    val dog1AndDog1 = TwoAnimals(dog1, dog1)
    val dog1AndDog1Json = om.writerWithDefaultPrettyPrinter().writeValueAsString(dog1AndDog1)
    println(dog1AndDog1Json)
    assert(dog1AndDog1 === om.readValue(dog1AndDog1Json, TwoAnimals::class.java)) // DESERIALIZE FAILS
}

Then I run the main function I get the following output:

{
  "animal1" : {
    "@class" : "some.test.Dog",
    "@id" : 1,
    "name" : "Dog1"
  },
  "animal2" : 1
}

Followed by this exception:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class some.test.Animal]: missing type id property '@class' (for POJO property 'animal2')
 at [Source: (String)"{
  "animal1" : {
    "@class" : "some.test.Dog",
    "@id" : 1,
    "name" : "Dog1"
  },
  "animal2" : 1
}"; line: 7, column: 15] (through reference chain: some.test.TwoAnimals["animal2"])
    at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
<truncated rest of stacktrace>

It seems that Jackson expects an object at the animal2 property which has a @class property to find the correct class type to be deserialized. But it has been replaced with an id by the @JsonIdentityInfo annotation. Why does Jackson not look up the object by that id and then check the @class property of that instance?

I am not sure if this use case is not supported by Jackson or I am doing something wrong (what I am hoping for). Or maybe it is a bug?


Solution

  • I managed to to get it working by:

    1. Removing the @JsonIdentityInfo annotation from the Dog and Car sub classes
    2. Adding a @JsonIdentityInfo to the Animal base class