How do to make Jackson @JsonUnwrapped
annotation work properly in Kotlin data class for deserialisation?
Other than creation of a custom deserialiser for this class and adding it to object mapper as a module, I did not find any simple solution.
Here is my code example:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy::class)
@Schema(description = "Customer Details", allOf = [Address::class, Person::class])
data class CustomerDetails(
@JsonUnwrapped val addressDetails: Address?,// address related fields
@JsonUnwrapped val personDetails: Person? // person related fields
)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy::class)
data class Address(
val houseNumber: Int?,
val streetName: String?,
val city: String?
)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy::class)
data class Person(
val firstName: String?,
val lastName: String?,
val initials: String?
)
Because of the @Schema annotation, the swagger page works fine. Because of @JsonUnwrapped annotation, the serialisation of object to JSON works as intended. But the deserialisation fails with the error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot define Creator property "property name" as @JsonUnwrapped
: combination not yet supported
The combination of @JsonCreator
with @JsonUnwrapped
(which is said to be "not yet supported" in the exception message) will be supported in version 2.19. See this merged PR. I believe your code will work as is, if you use that version when it is released.
For now, I have found that adding a parameterless @JsonCreator
factory method works:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy::class)
data class CustomerDetails(
@field:JsonUnwrapped
val addressDetails: Address?,
@field:JsonUnwrapped
val personDetails: Person?
) {
companion object {
@JsonCreator
@JvmStatic
private fun make() = CustomerDetails(null, null)
}
}
Jackson will call make
and set the fields correctly.
Working example (using jackson-module-kotlin):
val json = "{\"houseNumber\":1,\"streetName\":\"a\",\"city\":\"b\",\"firstName\":\"c\",\"lastName\":\"d\",\"initials\":\"e\"}"
jacksonObjectMapper().readValue<CustomerDetails>(json).also { println(it) }
If you don't use jackson-module-kotlin, you can replace make
with a parameterless constructor instead (the Kotlin module doesn't call the parametersless constructor for some reason, which is why I had to use a factory method).