Skip to content

3.0 regression deserialization fails on nulls in nested structures under beans, even if type allows it #1064

@Incanus3

Description

@Incanus3

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions