FasterXML / jackson-module-kotlin

Module that adds support for serialization/deserialization of Kotlin (http://kotlinlang.org) classes and data classes.
Apache License 2.0
1.12k stars 175 forks source link

Kotlin default values do not work for missing fields when using polymorphism with JsonTypeInfo.As.EXTERNAL_PROPERTY #249

Open metarag opened 5 years ago

metarag commented 5 years ago
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.databind.annotation.NoClass
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

sealed class Entity
data class Bacteria(val speciesName: String): Entity()
data class Disease(val diseaseId: Long): Entity()

enum class EntityType {
    MY_Bacteria,
    MY_Disease
}

data class MyRequestBody(
    val entityType: EntityType,
    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
        property = "entityType",
        defaultImpl = NoClass::class,
        visible = true
    )
    @JsonSubTypes(
        JsonSubTypes.Type(value = Disease::class, name = "MY_Disease"),
        JsonSubTypes.Type(value = Bacteria::class, name = "MY_Bacteria")
    )
    val entity: Entity? = null
)

fun main() {
    val jsonString = """{
        |  "entityType": "MY_Bacteria"
        |}""".trimMargin()
    val reader = jacksonObjectMapper()
    val deserialized = reader.readValue<MyRequestBody>(jsonString)
    println(deserialized)
}

results in

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Missing property 'entity' for external type id 'entityType'
 at [Source: (String)"{
  "entityType": "MY_Bacteria"
}"; line: 3, column: 1]
    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
    at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1356)
    at com.fasterxml.jackson.databind.deser.impl.ExternalTypeHandler.complete(ExternalTypeHandler.java:281)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeUsingPropertyBasedWithExternalTypeId(BeanDeserializer.java:1001)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeWithExternalTypeId(BeanDeserializer.java:853)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:324)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3023)
    at MainKt.main(Main.kt:40)
    at MainKt.main(Main.kt)

However, if we change it to use JsonTypeInfo.As.PROPERTY, it works for the missing field

import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.databind.annotation.NoClass
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

sealed class Entity(val entityType: EntityType)
data class Bacteria(val speciesName: String): Entity(EntityType.MY_Bacteria)
data class Disease(val diseaseId: Long): Entity(EntityType.MY_Disease)

enum class EntityType {
    MY_Bacteria,
    MY_Disease
}

data class MyRequestBody(
    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "entityType",
        defaultImpl = NoClass::class,
        visible = true
    )
    @JsonSubTypes(
        JsonSubTypes.Type(value = Disease::class, name = "MY_Disease"),
        JsonSubTypes.Type(value = Bacteria::class, name = "MY_Bacteria")
    )
    val entity: Entity? = null
)

fun main() {
    val jsonString = "{}"
    val reader = jacksonObjectMapper()
    val deserialized = reader.readValue<MyRequestBody>(jsonString)
    println(deserialized)
}

It successfully prints MyRequestBody(entity=null)

cowtowncoder commented 5 years ago

Sounds like this is Kotlin-specific, will transfer.

cutiko commented 4 years ago

I'm having the same problem but instead of null I get the default class I'm using

@metarag did you found a solution you could share?