When I am deserialising into my class, for some fields, I would like to be able to tell the difference between the value being absent, or null. For example, {"id": 5, "name": null}
should be considered different to {"id": 5}
.
I have come across solutions for kotlinx.serialisation and Rust's serde, but so far, I'm struggling to achieve this in Jackson.
I'll use this class as an example:
data class ResponseJson(
val id: Int,
@JsonDeserialize(using = OptionalPropertyDeserializer::class)
val name: OptionalProperty<String?>
)
The definition of the OptionalProperty field:
sealed class OptionalProperty<out T> {
object Absent : OptionalProperty<Nothing>()
data class Present<T>(val value: T?) : OptionalProperty<T>()
}
I've written a custom deserialiser:
class OptionalPropertyDeserializer :
StdDeserializer<OptionalProperty<*>>(OptionalProperty::class.java),
ContextualDeserializer
{
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): OptionalProperty<*> {
println(p.readValueAs(ctxt.contextualType.rawClass))
return OptionalProperty.Present(p.readValueAs(ctxt.contextualType.rawClass))
}
override fun getNullValue(ctxt: DeserializationContext?) = OptionalProperty.Present(null)
override fun getAbsentValue(ctxt: DeserializationContext?) = OptionalProperty.Absent
override fun createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer<*> {
println(property.type.containedType(0))
return ctxt.findContextualValueDeserializer(property.type.containedType(0), property)
}
}
Finally, my ObjectMapper setup:
val messageMapper: ObjectMapper = jacksonObjectMapper()
.disable(ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.disable(ACCEPT_FLOAT_AS_INT)
.enable(FAIL_ON_NULL_FOR_PRIMITIVES)
.enable(FAIL_ON_NUMBERS_FOR_ENUMS)
.setSerializationInclusion(NON_EMPTY)
.disable(WRITE_DATES_AS_TIMESTAMPS)
Now, I try to deserialise some JSON:
@Test
fun deserialiseOptionalProperty() {
assertEquals(
ResponseJson(5, OptionalProperty.Present("fred")),
messageMapper.readValue(
//language=JSON
"""
{
"id": 5,
"name": "fred"
}
""".trimIndent()
)
)
}
I am getting the following exception:
com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `serialisation_experiments.JacksonTests$ResponseJson`, problem: argument type mismatch
at [Source: (String)"{
"id": 5,
"name": "fred"
}"; line: 4, column: 1]
What does "argument type mismatch" mean here? I assume I've done something incorrectly with the custom deserialiser, but what is the correct approach?