-
Notifications
You must be signed in to change notification settings - Fork 175
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
[Bug] A data class that has an inline class-typed property can't be deserialized #413
Comments
This is a well-known issue, but I didn't realize that it had to do with the constructors being synthetic! Is there no way for Jackson to find synthetic stuff at runtime? |
FWIW I'm running into what looks like the same issue using a Kotlin Kotlin: 1.5.0-RC import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
@JvmInline
value class MyInlineClass(val i: Int)
data class MyDataClass(val i: MyInlineClass)
data class MyDataClassTwoProps(
// This annotation seems to be required due to the property name getting mangled
@get:JsonProperty(value = "i")
val i: MyInlineClass,
@get:JsonProperty(value = "j")
val j: MyInlineClass,
)
fun main() {
val json = objectMapper.writeValueAsString(MyDataClassTwoProps(MyInlineClass(1), 2))
println(json)
val deserialized = objectMapper.readValue<MyDataClassTwoProps>(json)
println(deserialized)
val json2 = objectMapper.writeValueAsString(MyDataClass(MyInlineClass(1)))
println(json2)
val deserialized2 = objectMapper.readValue<MyDataClass>(json2)
println(deserialized2)
} Interestingly enough, the data class with two properties does deserialize correctly, regardless of the type of the second property (either a value/inline class or a normal class). Only the data class with a single |
@dvail
and see what happens. The above is from memory, so I'm not sure if the delegating mode constant is uppercase or what. You may or may not also need that JsonProperty annotation. |
What @ragnese said, with just one twist: So I guess that if you want behavior similar to 2- or 3-argument case, you would add:
|
Sorry- yes, @cowtowncoder is right. I was thinking with the mindset of wanting the class to act like a wrapper, which is what I was just doing recently. To have it act like a nested object, you'd use PROPERTIES |
Thanks for the tips! Unfortunately I was unable to get this working with either @JvmInline
value class MyInlineClass(private val i: Int)
data class MyDataClass @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) constructor(
@get:JsonProperty(value = "i")
val i: MyInlineClass,
)
fun main() {
val json = objectMapper.writeValueAsString(MyDataClass(MyInlineClass(42)))
println(json)
val deserialized = objectMapper.readValue<MyDataClass>(json)
println(deserialized)
} What did serve as a workaround in my case though was defining a static create method on the data class as described in this comment: @JvmInline
value class MyInlineClass(private val i: Int)
data class MyDataClass(
@get:JsonProperty(value = "i")
val test: MyInlineClass,
) {
companion object {
@JsonCreator
@JvmStatic
fun create(test: Int) = MyDataClass(MyInlineClass(test))
}
} The key thing here was that the name of the parameter passed to |
One comment on test: please avoid "write then read immediately" construct on reproductions; or, if using both, verify both intermediate JSON and resulting object. This way it is possible to reason about case more easily. This because serialization and deserialization sides are related but not closely coupled, so it is usually necessary to consider them separately. Similarly, instead of printing out things, assertions are good as they point out expectations: showing just what happens does not always tell what your expectation was (and verbal descriptions are more ambiguous than assert statements). |
Here is a complete test code based on the previous message of @dvail Tested module: import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@JvmInline
value class MyInlineClass(val i: Int)
data class MyDataClass(val i: MyInlineClass)
data class MyDataClassTwoProps(
@get:JsonProperty(value = "i")
val i: MyInlineClass,
@get:JsonProperty(value = "j")
val j: MyInlineClass,
)
data class MyNormalDataClass(val i: Int)
class ObjectMapperInlineClassTest {
private val objectMapper = jacksonObjectMapper()
@Test
fun `data class without inline class`() {
val obj = MyNormalDataClass(1)
val json = objectMapper.writeValueAsString(obj)
Assertions.assertEquals("{\"i\":1}", json)
val deserialized = objectMapper.readValue<MyNormalDataClass>(json) // works
Assertions.assertEquals(obj, deserialized)
}
@Test
fun `data class with one inline class`() {
val obj = MyDataClass(MyInlineClass(1))
val json = objectMapper.writeValueAsString(obj)
Assertions.assertEquals("{\"i\":1}", json)
val deserialized = objectMapper.readValue<MyDataClass>(json) // throws MismatchedInputException exception
Assertions.assertEquals(obj, deserialized)
}
@Test
fun `data class with two inline class`() {
val obj = MyDataClassTwoProps(MyInlineClass(1), MyInlineClass(2))
val json = objectMapper.writeValueAsString(obj)
Assertions.assertEquals("{\"i\":1,\"j\":2}", json)
val deserialized = objectMapper.readValue<MyDataClassTwoProps>(json) // throws MismatchedInputException exception
Assertions.assertEquals(obj, deserialized)
}
} |
I had some tests. For data class with 2+ inline value fields is OK with the following module register. import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule
import io.quarkus.test.junit.QuarkusTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import javax.inject.Inject
@JvmInline
value class MyInlineClass(val i: Int)
data class MyDataClass(val i: MyInlineClass, @JsonIgnore val _i:Int=0)
data class MyDataClassTwoProps(
val i: MyInlineClass,
val j: MyInlineClass
)
data class MyNormalDataClass(val i: Int)
class ObjectMapperInlineClassTest {
val objectMapper : ObjectMapper = jacksonObjectMapper()
.registerModule(Jdk8Module()).registerModule(ParameterNamesModule())
@Test
fun `data class without inline class`() {
val obj = MyNormalDataClass(1)
val json = objectMapper.writeValueAsString(obj)
Assertions.assertEquals("""{"i":1}""", json)
val deserialized = objectMapper.readValue(json,MyNormalDataClass::class.java) // works
Assertions.assertEquals(obj, deserialized)
}
@Test
fun `data class with one inline class`() {
val obj = MyDataClass(MyInlineClass(1))
val json = objectMapper.writeValueAsString(obj)
Assertions.assertEquals("""{"i":1}""", json)
val deserialized = objectMapper.readValue(json,MyDataClass::class.java)
Assertions.assertEquals(obj, deserialized)
}
@Test
fun `data class with two inline class`() {
val obj = MyDataClassTwoProps(MyInlineClass(1), MyInlineClass(2))
val json = objectMapper.writeValueAsString(obj)
Assertions.assertEquals("""{"i":1,"j":2}""", json)
val deserialized = objectMapper.readValue(json,MyDataClassTwoProps::class.java) // throws MismatchedInputException exception
Assertions.assertEquals(obj, deserialized)
}
} |
I am working on deserialization support for I will basically report on the progress of this project in #199. |
This issue is closed as the issue regarding deserialization support for |
Describe the bug
A data class that has an inline class-typed property can't be deserialized.
To Reproduce
Run:
Expected behavior
The JSON is properly deserialized.
Actual behavior
Or, if there are multiple fields in the data class:
Versions
Kotlin: 1.4.21
Jackson-module-kotlin: 2.12.1
Jackson-databind: 2.12.1
Additional context
I've tried to dig through this and discovered that adding an inline class-typed property causes some constructor changes - the compiled data class has two constructors:
The second one is ignored by Jackson because it's synthetic. The first one is correctly discovered, however, that constructor is invisible through Kotlin reflection, so Jackson-module-kotlin fails to discover the parameter names and is not able to use that constructor.
The problem can not be worked around by using
@JsonProperty
because Kotlin compiler places it onto the parameters of the synthetic constructor, and no annotations appear on the private constructor.The text was updated successfully, but these errors were encountered: