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 176 forks source link

@JsonAnySetter not working since 2.18, what is the correct usage? #832

Open jacinpoz opened 3 days ago

jacinpoz commented 3 days ago

Your question

Since the upgrade to 2.18.0 from 2.17.2 our data classes constructor properties are not deserialized correctly.

See sample code below:

class TestAnySetter {
    data class AnySetter @JvmOverloads constructor(
        val test : String? = null,
        @field:JsonAnySetter
        @get:JsonAnyGetter
        val anything: Map<String, Any?> = mutableMapOf(),
    )

    @Test
    fun testDeserialization() {
        val json = """
            {
                "widget": {
                    "debug": "on"
                 }
             }     """.trimMargin()
        val jacksonMapper = ObjectMapper()
        jacksonMapper.registerModules(KotlinModule.Builder().build())
        val anySetter = jacksonMapper.readValue<AnySetter>(json)
        assertEquals("widget", anySetter.anything.entries.first().key)
    }
}

This test passes in 2.17.2, but in 2.18 the "anything" property in "anySetter" variable doesn't contain any entries in the map and therefore it throws a java.util.NoSuchElementException.

Additionally, when we upgraded to 2.17, we had to change all references to @JsonAnySetterin our constructor properties to be @field:JsonAnySetter as otherwise no json properties would be deserialized. Now none of these approaches in 2.18 work.

What is the correct way of using @JsonAnySetter in constructor properties now?

PS: I also played with the @JsonCreator annotation given the contents of https://cowtowncoder.medium.com/jackson-2-18-rc1-overview-765e29a33371 , but it made no difference.

cowtowncoder commented 2 days ago

@jacinpoz This might be due to:

https://github.com/FasterXML/jackson-databind/issues/4508

and if so, it'd be fixed for 2.18.1. If there is any chance you could try locally building and using 2.18.1-SNAPSHOT of jackson-databind, that'd be great. To rule fix in (or out).

Breakage in 2.18.0 was uncaught because it appears that:

  1. This is not a use case covered by any of Kotlin module unit tests (contributions welcome!)
  2. No one with this use case tried out 2.18.0-rc1 version that was available before final 2.18.0
jacinpoz commented 2 days ago

@cowtowncoder Thank you for the very quick response!

I have tested locally with jackson-databind 2.18.1 and it seems to work well when using @JsonAnySetter, although it doesn't work anymore with @field:JsonAnySetter . So basically we are back to the same behaviour we had in 2.16, which is fine by me!

Thanks again for the rapid reply, and I am looking forward to seeing 2.18.1 released.

chad-moller-target commented 2 days ago

@jacinpoz for the benefit of others who find this bug, would you be willing to share the example code that works with 2.18.1-SNAPSHOT?

jacinpoz commented 2 days ago

Sure, it's just the exact same code as the one in my first example, but replacing @field:JsonAnySetter for @JsonAnySetter. See code below:

class TestAnySetter {
    data class AnySetter @JvmOverloads constructor(
        val test : String? = null,
        @JsonAnySetter
        @get:JsonAnyGetter
        val anything: Map<String, Any?> = mutableMapOf(),
    )

    @Test
    fun testDeserialization() {
        val json = """
            {
                "widget": {
                    "debug": "on"
                 }
             }     """.trimMargin()
        val jacksonMapper = ObjectMapper()
        jacksonMapper.registerModules(KotlinModule.Builder().build())
        val anySetter = jacksonMapper.readValue<AnySetter>(json)
        assertEquals("widget", anySetter.anything.entries.first().key)
    }
}