Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserialization fails for local data class with local default value #712

Open
1 task done
IlyaNerd opened this issue Sep 26, 2023 · 3 comments
Open
1 task done
Labels

Comments

@IlyaNerd
Copy link

IlyaNerd commented Sep 26, 2023

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

@IlyaNerd IlyaNerd added the bug label Sep 26, 2023
@k163377 k163377 changed the title Serialization fails for local data class with local default value Deserialization fails for local data class with local default value Sep 30, 2023
@k163377
Copy link
Contributor

k163377 commented Sep 30, 2023

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.

@IlyaNerd
Copy link
Author

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)

@k163377
Copy link
Contributor

k163377 commented Oct 10, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants