-
-
Notifications
You must be signed in to change notification settings - Fork 180
Open
Labels
Description
Search before asking
- I searched in the issues and found nothing similar.
- I have confirmed that the same problem is not reproduced if I exclude the KotlinModule.
- I searched in the issues of databind and other modules used and found nothing similar.
- I have confirmed that the problem does not reproduce in Java and only occurs when using Kotlin and KotlinModule.
Describe the bug
since 3.0, if I have a type like data class RegistryJsonApiResponse(val unwrapped: List<Map<String, Any?>>?), deserialization would fail with InvalidNullException if the inner Map contains a null value, even though the type allows it.
To Reproduce
import org.junit.jupiter.api.Test
import tools.jackson.module.kotlin.jsonMapper
import tools.jackson.module.kotlin.kotlinModule
import tools.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.jsonMapper as jsonMapper2
import com.fasterxml.jackson.module.kotlin.kotlinModule as kotlinModule2
import com.fasterxml.jackson.module.kotlin.readValue as readValue2
data class RegistryJsonApiResponse(
val wrapped: Map<String, List<Map<String, Any?>>>?,
val unwrapped: List<Map<String, Any?>>?,
val plain: Map<String, Any?>?,
)
class JacksonNullsInNestedStructureTest {
@Test
fun `nulls in nested structures`() {
val plain = """
{
"plain": {
"target":null
}
}
""".trimIndent()
jsonMapper2().readValue2<RegistryJsonApiResponse>(plain) // this fails
jsonMapper2 { addModule(kotlinModule2()) }.readValue2<RegistryJsonApiResponse>(plain) // this passes
jsonMapper().readValue<RegistryJsonApiResponse>(plain) // this passes
jsonMapper { addModule(kotlinModule()) }.readValue<RegistryJsonApiResponse>(plain) // this passes
val unwrapped = """
{
"unwrapped": [
{
"target":null
}
]
}
""".trimIndent()
jsonMapper2().readValue2<RegistryJsonApiResponse>(unwrapped) // this fails
jsonMapper2 { addModule(kotlinModule2()) }.readValue2<RegistryJsonApiResponse>(unwrapped) // this passes
jsonMapper().readValue<RegistryJsonApiResponse>(unwrapped) // this passes
jsonMapper { addModule(kotlinModule()) }.readValue<RegistryJsonApiResponse>(unwrapped) // this fails
val wrapped = """
{
"wrapped": {
"SourceEntity": [
{
"target":null
}
]
}
}
""".trimIndent()
jsonMapper2().readValue2<RegistryJsonApiResponse>(wrapped) // this fails
jsonMapper2 { addModule(kotlinModule2()) }.readValue2<RegistryJsonApiResponse>(wrapped) // this passes
jsonMapper().readValue<RegistryJsonApiResponse>(wrapped) // this passes
jsonMapper { addModule(kotlinModule()) }.readValue<RegistryJsonApiResponse>(wrapped) // this fails
}
}Expected behavior
the last readValue in each group should pass.
I've included 4 readValue calls in each group:
- version 2 with kotlin module not registered
- version 2 with kotlin module registered
- version 3 with kotlin module not registered
- version 3 with kotlin module registered
and the results are
- the first call never succeeds - seems v2 just can't handle this case at all w/o kotlin module
- the second call always succeeds - v2 with kotlin module handles all of these cases correctly
- the third call, interestingly enough, also always succeeds - seems that v3 can handle these cases w/o kotlin module
- the fourth call only succeeds in the first case. second and third are broken in v3 with kotlin module
Versions
Kotlin:
Jackson-module-kotlin: 2.20.1 and 3.0.2
Jackson-databind: 2.14.2 and 3.0.2
Additional context
the stack trace is
Invalid `null` value encountered for property "unwrapped"
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN] (through reference chain: cz.sentica.qwazar.base.RegistryJsonApiResponse["unwrapped"]->java.util.ArrayList[0]->java.util.LinkedHashMap["target"])
tools.jackson.databind.exc.InvalidNullException: Invalid `null` value encountered for property "unwrapped"
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN] (through reference chain: cz.sentica.qwazar.base.RegistryJsonApiResponse["unwrapped"]->java.util.ArrayList[0]->java.util.LinkedHashMap["target"])
at tools.jackson.databind.exc.InvalidNullException.from(InvalidNullException.java:49)
at tools.jackson.databind.deser.impl.NullsFailProvider.getNullValue(NullsFailProvider.java:46)
at tools.jackson.databind.deser.jdk.MapDeserializer._readAndBindStringKeyMap(MapDeserializer.java:605)
at tools.jackson.databind.deser.jdk.MapDeserializer.deserialize(MapDeserializer.java:428)
at tools.jackson.databind.deser.jdk.MapDeserializer.deserialize(MapDeserializer.java:30)
at tools.jackson.databind.deser.jdk.CollectionDeserializer._deserializeNoNullChecks(CollectionDeserializer.java:493)
at tools.jackson.databind.deser.jdk.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:348)
at tools.jackson.databind.deser.jdk.CollectionDeserializer.deserialize(CollectionDeserializer.java:238)
at tools.jackson.databind.deser.jdk.CollectionDeserializer.deserialize(CollectionDeserializer.java:29)
at tools.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:552)
at tools.jackson.databind.deser.bean.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:746)
at tools.jackson.databind.deser.bean.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:592)
at tools.jackson.databind.deser.bean.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1417)
at tools.jackson.databind.deser.bean.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:480)
at tools.jackson.databind.deser.bean.BeanDeserializer.deserialize(BeanDeserializer.java:200)
at tools.jackson.databind.deser.DeserializationContextExt.readRootValue(DeserializationContextExt.java:265)
at tools.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2610)
at tools.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1543)
at cz.sentica.qwazar.base.JacksonNullsInNestedStructureTest.nulls in nested structures(JacksonNullsInNestedStructureTest.kt:133)