FasterXML / jackson-module-kotlin

Module that adds support for serialization/deserialization of Kotlin (http://kotlinlang.org) classes and data classes.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Deserialization fails for local data class with local default value

IlyaNerd opened this issue · comments

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

When I try to serialize json to local data class, which also has a param with default value taken from local variable - it fails with com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition

It works if the default value is set as is or if default value is coming from top level variable or from class local variable.

To Reproduce

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue

val defaultTopLevelValue = "top level"

object Main {
    val defaultInClass = "class"

    @JvmStatic
    fun main(args: Array<String>) {
        val json = ObjectMapper().registerModule(KotlinModule())
        val defaultLocalValue = "local"

        data class Local(val a: String = defaultLocalValue)
        data class InClass(val a: String = defaultInClass)
        data class TopLevel(val a: String = defaultTopLevelValue)
        data class Const(val a: String = "const")

        val str = """{}"""
        json.readValue<TopLevel>(str).also(::println) // works
        json.readValue<InClass>(str).also(::println) // works
        json.readValue<Const>(str).also(::println) // works
        json.readValue<Local>(str).also(::println) // fails - Invalid type definition for type `com.sedex.connect.questionnaire.pact.Main$main$Local`: Argument #0 of constructor [constructor for `com.sedex.connect.questionnaire.pact.Main$main$Local` (2 args), annotations: [null] has no property name (and is not Injectable): can not use as property-based Creator
    }
}

stacktrace:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `com.sedex.connect.questionnaire.pact.Main$main$Local`: Argument #0 of constructor [constructor for `com.sedex.connect.questionnaire.pact.Main$main$Local` (2 args), annotations: [null] has no property name (and is not Injectable): can not use as property-based Creator
 at [Source: (String)"{}"; line: 1, column: 1]
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:62)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadTypeDefinition(DeserializationContext.java:1893)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._validateNamedPropertyParameter(BasicDeserializerFactory.java:1162)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addExplicitPropertyCreator(BasicDeserializerFactory.java:879)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addExplicitAnyCreator(BasicDeserializerFactory.java:926)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addExplicitConstructorCreators(BasicDeserializerFactory.java:466)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:293)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:222)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:262)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:151)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:415)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:350)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
	at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
	at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:654)
	at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4956)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4826)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3772)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3755)
	at com.sedex.connect.questionnaire.pact.Main.main(Test.kt:71)

Expected behavior

data class is correctly serialized without failures

Versions

Kotlin: 1.8.20
Jackson-module-kotlin: 2.15.2
Jackson-databind: 2.15.2

Additional context

No response

I have investigated, but it seems impractical to fix this problem.

The constructor of the class defined as such was compiled on the JVM to require two arguments.
The meaning of the one additional argument was not clear to me, as bytecode display and decompilation failed, but annoyingly, it seems to require this argument to be non null.

In order for this to work, a lot of changes would have to be made in critical areas.
On the other hand, this situation is very rare and appears to be an edge case that is easy to work around.
Personally, the benefits gained seem too small compared to the effort and risk of the change.

Yeah, i agree, rare case with easy workaround. The only problem is that you dont really know what went wrong, so first you need to spend quite some time to debug and narrow the issue and only then the workarounds become easy.

So mb there is a chance to improve the error? (if the fix is too complicated)

I have been thinking about it, but haven't come up with a way to handle it well.
There is an entire process that assumes that the number of parameters in the constructor matches the number of parameters in Kotlin, so it would take a bit of work to change all of those processes to make this obvious.

I don't intend to make this a priority for now because of other things I have to do.