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

Serialization and Deserialization of Kotlin `data class` fails on PolymorphicTypeValidator with Any #819

Open effx13 opened 1 month ago

effx13 commented 1 month ago

Search before asking

Describe the bug

When trying to deserialize class that contains kotlin value class, Jackson throws InvalidTypeIdException with missing type id property '@class' on ObjectMapper.DefaultTyping.NON_FINAL Despite adding @JsonCreator annotation, I got the same result.

And trying to serialize above class with ObjectMapper.DefaultTyping.NON_FINAL, Jackson throws JsonMappingException with class ServerName cannot be cast to class java.lang.String (ServerName is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap') (through reference chain: TestDto["serverName"])

I'm using PolymorphicTypeValidator and Any class to do serialization and deserialization in Redis. But no matter what settings and annotations I use, the serialization and deserialization fails.

Version Information

JVM 21 Kotlin 1.8

jackson-core:2.17.2 jackson-databind:2.17.2 jackson-annotations:2.17.2 jackson-datatype-jsr310:2.17.2 jackson-module-kotlin:2.17.2

Reproduction

@JvmInline
value class ServerName(
  val value: String,
) {
  companion object {
    @JsonCreator
    @JvmStatic
    fun fromValue(value: String): ServerName {
      return ServerName(value)
    }
  }
}

data class TestDto(
  val id: Int,
  val serverName: ServerName
)

val objectMapper = ObjectMapper()
  .registerKotlinModule()
  .registerModule(JavaTimeModule())
  .activateDefaultTyping(
    BasicPolymorphicTypeValidator.builder().allowIfBaseType(Any::class.java).build(),
    ObjectMapper.DefaultTyping.NON_FINAL, // or EVERYTHING
    JsonTypeInfo.As.PROPERTY,
  )

// on NON_FINAL
fun main() {
  val serverName = ServerName("TEST")
  val testDto = TestDto(1, serverName)

  val serialized = objectMapper.writeValueAsBytes(testDto)
  println(String(serialized))
  // Expected output: {"id":1,"serverName":"TEST"}
  // Actual output: {"id":1,"serverName":{"value":"TEST"}}

  val deserialized = objectMapper.readValue(serialized, Any::class.java) // Because of RedisSerializer
  println(deserialized)
  // Expected output: TestDto(id=1, serverName=ServerName(value=TEST))
  // Actual output: Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'
}

// on EVERYTHING
fun main() {
  val serverName = ServerName("TEST")
  val testDto = TestDto(1, serverName)

  val serialized = objectMapper.writeValueAsBytes(testDto)
  println(String(serialized))
  // Expected output: {"id":1,"serverName":"TEST"}
  // Actual output: {"id":1,"serverName":{"value":"TEST"}}

  val deserialized = objectMapper.readValue(serialized, Any::class.java) // Because of RedisSerializer
  println(deserialized)
  // Expected output: TestDto(id=1, serverName=ServerName(value=TEST))
  // Actual output: Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: class ServerName cannot be cast to class java.lang.String (ServerName is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap') (through reference chain: TestDto["serverName"])
}

Expected behavior

No response

Additional context

No response

cowtowncoder commented 1 month ago

Kotlin issues belong under jackson-module-kotlin in general, will transfer.