FasterXML / FasterXML/jackson-module-kotlin
3.0 regression deserialization fails on nulls in nested structures under beans, even if type allows it
- Dominant language
- Kotlin
- Stars
- 1.2k
- Forks
- 187
- Avg merge
- 9h 48m
- Merged PRs (30d)
- 16
Description
### Search before asking
- [x] I searched in the [issues](https://github.com/FasterXML/jackson-module-kotlin/issues) and found nothing similar.
- [x] I have confirmed that the same problem is not reproduced if I exclude the KotlinModule.
- [x] I searched in the [issues of databind](https://github.com/FasterXML/jackson-databind/issues) 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>?)`, deserialization would fail with `InvalidNullException` if the inner `Map` contains a null value, even though the type allows it.
### To Reproduce
```kotlin
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>>?,
val unwrapped: List>?,
val plain: Map?,
)
class JacksonNullsInNestedStructureTest {
@Test
fun `nulls in nested structures`() {
val plain = """
{
"plain": {
"target":null
}
}
""".trimIndent()
jsonMapper2().readValue2(plain) // this fails
jsonMapper2 { addModule(kotlinModule2()) }.readValue2(plain) // this passes
jsonMapper().readValue(plain) // this passes
jsonMapper { addModule(kotlinModule()) }.readValue(plain) // this passes
val unwrapped = """
{
"unwrapped": [
{
"target":null
}
]
}
""".trimIndent()
jsonMapper2().readValue2(unwrapped) // this fails
jsonMapper2 { addModule(kotlinModule2()) }.readValue2(unwrapped) // this passes
jsonMapper().readValue(unwrapped) // this passes
jsonMapper { addModule(kotlinModule()) }.readValue(unwrapped) // this fails
val wrapped = """
{
"wrapped": {
"SourceEntity": [
{
"target":null
}
]
}
}
""".trimIndent()
jsonMapper2().readValue2(wrapped) // this fails
jsonMapper2 { addModule(kotlinModule2()) }.readValue2(wrapped) // this passes
jsonMapper().readValue(wrapped) // this passes
jsonMapper { addModule(kotlinModule()) }.readValue(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)
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.