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

If a default value is defined, JacksonInject is ignored. #722

Closed k163377 closed 9 months ago

k163377 commented 10 months ago

Search before asking

Describe the bug

As shown below, if it is missing and optional, it will be automatically skipped. https://github.com/FasterXML/jackson-module-kotlin/blob/e9463e92ba96a34cc355f5bd99776ac435dbb184/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt#L70

On the other hand, processing related to JacksonInject is further back and should not be skipped.

To Reproduce

import com.fasterxml.jackson.annotation.JacksonInject
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.InjectableValues
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals

class GitHubXXX {
    data class FailingDto @JsonCreator constructor(
        @JacksonInject("foo")
        @JsonProperty("foo")
        val foo: Int = 100,
        @JacksonInject("bar")
        @JsonProperty("bar")
        val bar: Int? = 200
    )

    val injectValues = mapOf("foo" to 1, "bar" to 2)
    val expected = FailingDto(1, 2)

    @Test
    fun onPlainMapper() {
        // Succeeds in plain mapper
        val plainMapper = ObjectMapper()
        assertEquals(
            expected,
            plainMapper.readerFor(FailingDto::class.java)
                .with(InjectableValues.Std(injectValues))
                .readValue("{}")
        )
    }

    @Test
    fun failing() {
        // The kotlin mapper uses the Kotlin default value instead of the Inject value.
        val kotlinMapper = jacksonObjectMapper()
        val result = kotlinMapper.readerFor(FailingDto::class.java)
            .with(InjectableValues.Std(injectValues))
            .readValue<FailingDto>("{}")

        assertNotEquals(result, expected, "GitHubXXX fixed.")
        assertEquals(FailingDto(), result)
    }

    data class WithoutDefaultValue(
        @JacksonInject("foo")
        val foo: Int,
        @JacksonInject("bar")
        val bar: Int?
    )

    @Test
    fun withoutDefaultValue() {
        val kotlinMapper = jacksonObjectMapper()
        val result = kotlinMapper.readerFor(WithoutDefaultValue::class.java)
            .with(InjectableValues.Std(injectValues))
            .readValue<WithoutDefaultValue>("{}")

        // If there is no default value, the problem does not occur.
        assertEquals(WithoutDefaultValue(1, 2), result)
    }
}

Expected behavior

The value set by JacksonInject should be used.

Versions

As far as Blame is concerned, it appears that this behavior has not been changed in over 6 years. https://github.com/FasterXML/jackson-module-kotlin/blame/e9463e92ba96a34cc355f5bd99776ac435dbb184/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt#L70

Additional context

This problem can be avoided by dropping the use of default values in the creator.