jackson-module-kotlin icon indicating copy to clipboard operation
jackson-module-kotlin copied to clipboard

not-null requirement ignored for primitives in data classes

Open magicfoodhand opened this issue 6 years ago • 6 comments

When deserializing a data class with primitive types, the default value for that java primitive type is used instead of throwing an IllegalArgumentException or MissingKotlinParameterException.

Steps to reproduce

  1. Create new project using maven archetype - org.jetbrains.kotlin:kotlin-archetype-jvm
  2. Add dependency on jackson-module-kotlin - 2.9.7
  3. Run the following test class
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.junit.Test
import java.lang.IllegalArgumentException
import kotlin.test.assertNull

data class NullIntValue(val value: Int?)
data class NullDoubleValue(val value: Double?)
data class NullLongValue(val value: Long?)
data class NullBooleanValue(val value: Boolean?)

data class IntValue(val value: Int)
data class DoubleValue(val value: Double)
data class LongValue(val value: Long)
data class BooleanValue(val value: Boolean)

class DeserializationTest {
    private val objectMapper = jacksonObjectMapper()

    @Test
    fun `test value is null - Boolean`(){
        val value : NullBooleanValue = objectMapper.readValue("{}")
        assertNull(value.value)
    }

    @Test
    fun `test value is null - Double`(){
        val value : NullDoubleValue = objectMapper.readValue("{}")
        assertNull(value.value)
    }

    @Test
    fun `test value is null - Int`(){
        val value : NullIntValue = objectMapper.readValue("{}")
        assertNull(value.value)
    }

    @Test
    fun `test value is null - Long`(){
        val value : NullLongValue = objectMapper.readValue("{}")
        assertNull(value.value)
    }

    @Test(expected = IllegalArgumentException::class)
    fun `test value throws - Boolean`(){
        val value : BooleanValue = objectMapper.readValue("{}")
        assertNull(value.value)
    }

    @Test(expected = IllegalArgumentException::class)
    fun `test value throws - Double`(){
        val value : DoubleValue = objectMapper.readValue("{}")
        assertNull(value.value)
    }

    @Test(expected = IllegalArgumentException::class)
    fun `test value throws - Int`(){
        val value : IntValue = objectMapper.readValue("{}")
        assertNull(value.value)
    }

    @Test(expected = IllegalArgumentException::class)
    fun `test value throws - Long`(){
        val value : LongValue = objectMapper.readValue("{}")
        assertNull(value.value)
    }
}

magicfoodhand avatar Jul 25 '19 21:07 magicfoodhand

I'm also hitting this.

mikeholler avatar Oct 10 '19 18:10 mikeholler

@JsonProperty(required = true) seems to help.

mikeholler avatar Oct 10 '19 18:10 mikeholler

I found a bunch of threads relating to this issue:

  • https://stackoverflow.com/questions/49900920/kotlin-can-i-force-not-nullable-long-to-be-represented-as-non-primitive-type-in
  • https://github.com/FasterXML/jackson-module-kotlin/issues/26
  • https://github.com/FasterXML/jackson-module-kotlin/issues/29
  • https://github.com/FasterXML/jackson-databind/pull/1224

mikeholler avatar Oct 10 '19 18:10 mikeholler

I think that it should be possible to make Kotlin-specific AnnotationIntrospector "find" required-ness for properties of data classes.

cowtowncoder avatar Oct 10 '19 19:10 cowtowncoder

I've hit this as well :(

Any work on this?

serandel avatar Sep 06 '20 18:09 serandel

Is this issue the content of this PR repair? #41

Just configure Jackson will solve the problem

jacksonObjectMapper().enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)

You can simply configure it in spring

spring:
  jackson:
    deserialization:
      FAIL_ON_NULL_FOR_PRIMITIVES: true

Nayacco avatar Mar 08 '21 08:03 Nayacco

This is the default behavior of databind and it seems inappropriate for kotlin-module to intervene in that process. However, I agree that it is counter-intuitive behavior.

@cowtowncoder Any ideas on this issue? Is enabling FAIL_ON_NULL_FOR_PRIMITIVES as GoldSubmarine suggests the only effective solution?

k163377 avatar Feb 22 '23 15:02 k163377

I think my only comment is that if Kotlin module could determine "requiredness" for properties, that could be used to force checks. Method in AnnotationIntrospector is:

    @Override
    public Boolean hasRequiredMarker(AnnotatedMember m) {
    }

and for core Jackson checks for @JsonProperty(required = true) but for Kotlin could use other mechanisms.

cowtowncoder avatar Feb 22 '23 18:02 cowtowncoder

It was closed as a duplicate of https://github.com/FasterXML/jackson-databind/issues/3392

k163377 avatar Sep 10 '23 06:09 k163377

I share a Kotlin version of the sample workaround shared below. https://github.com/FasterXML/jackson-databind/issues/3392#issuecomment-1712727837

    override fun refineDeserializationType(config: MapperConfig<*>, a: Annotated, baseType: JavaType): JavaType {
        return if (a is AnnotatedParameter && baseType.isPrimitive) {
            // As a workaround to the problem reported below,
            // if the parameter is a primitive type, deserialize it as a wrapper type.
            // https://github.com/FasterXML/jackson-module-kotlin/issues/242
            config.typeFactory.constructType(baseType.rawClass.kotlin.javaObjectType)
        } else {
            baseType
        }
    }

k163377 avatar Sep 10 '23 06:09 k163377