skip to Main Content

I’m just trying to achieve to deserialize a nested polymorphed data class but i’m not able to do so. The previous questions and answers didn’t help me much for achieving this task that looks simple. I still get this error no matter I add constructor, add more annotation etc. Very confused.

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Dog` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type"
)
@JsonSubTypes(
    JsonSubTypes.Type(value = Dog::class, name = "dog"),
)
sealed class Animal(val name: String)

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type"
)
@JsonSubTypes(
    JsonSubTypes.Type(value = Labrador::class, name = "labrador")
)
sealed class Dog(name: String, val breed: String) : Animal(name)

class Labrador(name: String, val color: String) : Dog(name, "Labrador")

fun main() {
    val json = """
    {
      "type": "dog",
      "name": "Buddy",
      "breed": "Labrador",
      "color": "golden"
    }
    """

    val objectMapper = jacksonObjectMapper()
    val animal: Animal = objectMapper.readValue(json)

    println(animal.javaClass) // class com.example.Dog
    println(animal.name) // Buddy
    println((animal as Dog).breed) // Labrador
    println((animal as? Labrador)?.color) // golden
}

2

Answers


  1. import com.fasterxml.jackson.annotation.*
    import com.fasterxml.jackson.module.kotlin.*
    
    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type"
    )
    @JsonSubTypes(
        JsonSubTypes.Type(value = Dog::class, name = "dog"),
        JsonSubTypes.Type(value = Labrador::class, name = "labrador")
    )
    sealed class Animal(val name: String)
    
    // Make Dog an interface instead of a sealed class
    interface Dog 
    
    data class Labrador(val name: String, val color: String, val breed: String = "Labrador") : Animal(name), Dog
    
    fun main() {
        val json = """
        {
          "type": "labrador",
          "name": "Buddy",
          "breed": "Labrador",
          "color": "golden"
        }
        """
    
        val objectMapper = jacksonObjectMapper()
        val animal: Animal = objectMapper.readValue(json)
    
        println(animal.javaClass) 
        println(animal.name) // Buddy
        if (animal is Labrador) {
            println(animal.breed) // Labrador
            println(animal.color) // golden
        }
    }
    
    Login or Signup to reply.
  2. I’m not a Jackson expert, but I think when you specify "type": "dog" Jackson tries to create an instance of class Dog, which is a sealed class. Sealed classes are always abstract, hence the exception. If you make Dog an open class instead of sealed (and add @JsonIgnoreProperties(ignoreUnknown = true) to ignore unknown property "color": "golden"), your code will should print:

    class Dog
    Buddy
    Labrador
    null
    

    If you want to get an instance of Labrador you have to change type: "type": "labrador", because type can not be both dog and labrador. Now it should print:

    class Labrador
    Buddy
    Labrador
    golden
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search