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

Jackson property annotations are not working with data classes #56

Closed
AAverin opened this issue Jan 20, 2017 · 15 comments
Closed

Jackson property annotations are not working with data classes #56

AAverin opened this issue Jan 20, 2017 · 15 comments

Comments

@AAverin
Copy link

AAverin commented Jan 20, 2017

@JsonUnwrapped annotations are not working for data classes failing with not find creator property with name 'propertyname'.
Same structure with a regular class seems to work fine.

@apatrida
Copy link
Member

can you please provide a small failing test case, thanks

@AAverin
Copy link
Author

AAverin commented Feb 3, 2017

Sorry, was on a vacation. Here's the test

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonUnwrapped
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.Before
import org.junit.Test

data class TestGalleryWidget(
        val widgetReferenceId: String,
        @JsonUnwrapped var gallery: TestGallery
)

@JsonInclude(JsonInclude.Include.NON_EMPTY)
data class TestGallery(
        val id: String? = null,
        val headline: String? = null,
        val intro: String? = null,
        val role: String? = null,
        val images: List<TestImage>? = null
)

@JsonInclude(JsonInclude.Include.NON_EMPTY)
data class TestImage(
        val id: String? = null,
        val escenicId: String? = null,
        val caption: String? = null,
        val copyright: String? = null,
        val crops: Map<String, String>? = null
)

class JacksonModuleKotlinTest {

    lateinit var mapper: ObjectMapper
    val gallery = TestGallery(
        id = "id",
        headline = "headline",
        intro = "intro",
        role = "role",
        images = listOf(
                TestImage(id = "testImage1"),
                TestImage(id = "testImage2")
        )
    )
    val validJson = """
{"widgetReferenceId":"widgetReferenceId","id":"id","headline":"headline","intro":"intro","role":"role","images":[{"id":"testImage1"},{"id":"testImage2"}]}
    """

    @Before
    fun setUp() {
        mapper = jacksonObjectMapper()
    }

    @Test
    fun serializes() {
        val result = mapper.writeValueAsString(TestGalleryWidget("widgetReferenceId", gallery))
        println(result)
    }

    @Test
    fun deserializes() {
        val result = mapper.readValue(validJson, TestGalleryWidget::class.java)
        println(result)
    }
}

@AAverin
Copy link
Author

AAverin commented Feb 3, 2017

deserializes test fails with:

com.fasterxml.jackson.databind.JsonMappingException: Could not find creator property with name 'gallery' (in class de.weltn24.natives.arnold.TestGalleryWidget)
 at [Source: 
{"widgetReferenceId":"widgetReferenceId","id":"id","headline":"headline","intro":"intro","role":"role","images":[{"id":"testImage1"},{"id":"testImage2"}]}
    ; line: 2, column: 1]

	at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
	at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:551)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:226)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:141)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:403)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
	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:476)
	at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3899)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3794)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
	at de.weltn24.natives.arnold.JacksonModuleKotlinTest.deserializes(JacksonModuleKotlinTest.kt:64)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:253)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

@apatrida
Copy link
Member

apatrida commented Feb 9, 2017

Thanks, looking at it now.

apatrida added a commit that referenced this issue Feb 9, 2017
@apatrida
Copy link
Member

apatrida commented Feb 9, 2017

The issue is obscured in older versions of Jackson, with 2.9 you now get the correct error:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Can not define Creator parameter 1 as `@JsonUnwrapped`: combination not yet supported
 at [Source: 
{"widgetReferenceId":"widgetReferenceId","id":"id","headline":"headline","intro":"intro","role":"role","images":[{"id":"testImage1"},{"id":"testImage2"}]}
    ; line: 2, column: 1]

Databind does not support JsonUnwrapped being used in a constructor or static creator method, it expects a 1-to-1 relationship between incoming parameters and available creator parameters. A solution would be to move that one property into the body of the class as a lateinit property. I show the bad, and good versions here in a test case added for this issue:

https://github.com/FasterXML/jackson-module-kotlin/blob/master/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/Github56.kt

@apatrida apatrida closed this as completed Feb 9, 2017
@sgrimm-sg
Copy link

A workaround for this, in case anyone arrives at this issue while searching for a solution to the problem: Change @JsonUnwrapped to @field:JsonUnwrapped, which will move the annotation to the field (instead of the constructor parameter) where Jackson will see it and correctly interpret it.

@cowtowncoder
Copy link
Member

This would suggest that there might be something wrong (or missing) with constructor parameter usage, or discovery. In theory unwrapped should work in either place, but if constructor is not visible for some reason then this could follow.

@nanodeath
Copy link

nanodeath commented Jan 6, 2018

Thanks @sgrimm-sg, that helped me out as well. I'm not even using data classes, but I am annotating a constructor property as @JsonUnwrapped -- as far as I can tell, @JsonUnwrapped must always be @field:JsonUnwrapped. I forgot to call registerKotlinModule() in my test! /facepalm nevermind...

@mjstewart
Copy link

I couldn't get @field:JsonUnwrapped to work. It works fine for serializing but not for deserializing.

To summarize what works and what doesn't.

This wont work
Naturally this is what you would think would work

@Embeddable
data class Sku(val sku: String)

@Entity
data class Product(@field:JsonUnwrapped val sku: Sku) {
    @field:[Id GeneratedValue(strategy = GenerationType.AUTO)]
    val id: Long? = null
}

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot define Creator property "sku" as @JsonUnwrapped: combination not yet supported

1. hack to make it work
This is the next best choice for it to work but is messy.

@Embeddable
data class Sku(val sku: String)

@Entity
data class Product(@field:JsonIgnore val sku: Sku) {
    @field:[Id GeneratedValue(strategy = GenerationType.AUTO)]
    val id: Long? = null

    @get:JsonUnwrapped
    private val _sku: Sku
        get() = sku
}

2. use lateinit

I don't consider this a solution as it violates the proper way to instantiate an object. For example, if the constructor has all value objects (non primitives) then you need to put in a dummy property placeholder for it to work with a data class.

@Embeddable
data class Sku(val sku: String)

@Entity
data class Product(val dummyPropertyToMakeThisWork: String) {
    @field:[Id GeneratedValue(strategy = GenerationType.AUTO)]
    val id: Long? = null

    @JsonUnwrapped lateinit var sku: Sku
}

ctfadmin pushed a commit to ctf/TEPID-commons that referenced this issue Apr 30, 2019
@javenwang
Copy link

1. hack to make it work
This is the next best choice for it to work but is messy.

@Embeddable
data class Sku(val sku: String)

@Entity
data class Product(@field:JsonIgnore val sku: Sku) {
    @field:[Id GeneratedValue(strategy = GenerationType.AUTO)]
    val id: Long? = null

    @get:JsonUnwrapped
    private val _sku: Sku
        get() = sku
}

it does not work for deserializing neither. here is my code:

data class Child(val name: String)

data class Parent(@field:JsonIgnore val child: Child) {
    @get:JsonUnwrapped
    private val _child: Child
        get() = child
}

error message:

java.lang.IllegalArgumentException: Instantiation of [simple type, class com.example.Parent] value failed for JSON property key due to missing (therefore NULL) value for creator parameter key which is a non-nullable type
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.example.Parent["child"])

	at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3750)
	at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3668)
	at io.vertx.core.json.JsonObject.mapTo(JsonObject.java:106)
	at com.example.AppTest.json unwrap deserialize(AppTest.kt:106)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.example.Parent] value failed for JSON property key due to missing (therefore NULL) value for creator parameter key which is a non-nullable type
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.example.Parent["child"])
	at com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:107)
	at com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator.build(PropertyBasedCreator.java:195)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:488)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1287)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
	at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3745)
	... 25 more

it seems that we have to use normal class for Parent rather than data class

class Parent {
    @JsonUnwrapped
    lateinit var child: Child
}

@hotzen
Copy link

hotzen commented Nov 16, 2020

i have only found the following "hack" to work properly and without too much dummy-fields-foobar:

data class Foobar @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: String) {
...
}

coming from #91 (comment)

@abhilashmandaliya
Copy link

abhilashmandaliya commented Jan 12, 2022

You saved my day @hotzen ! Thanks

@mellosti
Copy link

I am using second constructor with @JsonCreator as workaround:

data class Child(val name: String)

data class Parent(@field:JsonUnwrapped val child: Child) {
   @JsonCreator
   constructor(name: String): this(Child(name))
}

@wingsofovnia
Copy link

wingsofovnia commented Mar 24, 2024

While looking for a stop-gap solution for this in GitHub, I found this (usage) rather straightforward solution for @JsonUnwrapped support in Kotlin data classes without any need in adjusting fields/annotations. Support in creators is being worked on here: FasterXML/jackson-databind#1467 (PR: FasterXML/jackson-databind#4271).

@robsonhermes
Copy link

robsonhermes commented Mar 28, 2024

The issue is obscured in older versions of Jackson, with 2.9 you now get the correct error:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Can not define Creator parameter 1 as `@JsonUnwrapped`: combination not yet supported
 at [Source: 
{"widgetReferenceId":"widgetReferenceId","id":"id","headline":"headline","intro":"intro","role":"role","images":[{"id":"testImage1"},{"id":"testImage2"}]}
    ; line: 2, column: 1]

Databind does not support JsonUnwrapped being used in a constructor or static creator method, it expects a 1-to-1 relationship between incoming parameters and available creator parameters. A solution would be to move that one property into the body of the class as a lateinit property. I show the bad, and good versions here in a test case added for this issue:

https://github.com/FasterXML/jackson-module-kotlin/blob/master/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/Github56.kt

@apatrida Not sure if I missed something in your suggestion, but I tried an attribute inside the body of the data class, but didn't work. Tried in both data classes and "regular classes". When I add an @JsonUnwrapped attribute, the other fields from the body stop being deserialized:

@JsonIgnoreProperties(ignoreUnknown = true)
data class Foobar(
    val foo: String,
)  {
    var bar: String? = null

    @JsonUnwrapped
    var unwrappedFoobar: UnwrappedFoobar? = null

    data class UnwrappedFoobar(
        var unwrappedFoobar: String
    )
}

If I try to deserialize this Json:

        {
            "foo": "this is foo",
            "bar": "this is bar",
            "unwrappedFoobar": "this is unwrappedFoobar"
        }

Then the bar attribute will be null :(
As per my tests, everything in the body must be either wrapped or unwrapped. If you mix both, it doesn't deserialize the unwrapped ones. Serialization is fine. Is this a different issue? If yes I can open a separate one.

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

No branches or pull requests