not-null requirement ignored for primitives in data classes
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
- Create new project using maven archetype - org.jetbrains.kotlin:kotlin-archetype-jvm
- Add dependency on jackson-module-kotlin - 2.9.7
- 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)
}
}
I'm also hitting this.
@JsonProperty(required = true) seems to help.
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
I think that it should be possible to make Kotlin-specific AnnotationIntrospector "find" required-ness for properties of data classes.
I've hit this as well :(
Any work on this?
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
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?
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.
It was closed as a duplicate of https://github.com/FasterXML/jackson-databind/issues/3392
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
}
}