We use a lot of value objects with a single value. For (de-)serialization we use Jackson with the kotlin-module.
A example value object in Kotlin:
data class MyValueObject(val value: String)
or as Java
class MyValueObject {
private String value;
public MyValueObject(String value) { this.value = value; }
public String getValue() { return value; }
}
These value objects have to be serialized and deserialized and should be serialized "value only", eg. "theValue"
instead of "{"value":"theValue"}"
.
I would like to avoid writing custom serializers/deserializers for dozens of value objects.
I know for serialisation @JsonValue
can be used to realize the above.
data class MyValueObject(@JsonValue val value: String)
But the JSON ""theValue"" (the above serialized String-Literal) cannot be deserialized back as MyValueObject
. It sesults in the following exception:
com.fasterxml.jackson.databind.exc.MismatchedInputException:
Cannot construct instance of `[...].MyValueObject` (although at least one Creator exists):
no String-argument constructor/factory method to deserialize from String value ('test')
at [Source: (String)""test""; line: 1, column: 1]
Here is the Unit Test I used:
@Test
fun testSerialize() {
val objectMapper = ObjectMapper()
.registerModule(Jdk8Module())
.registerModule(KotlinModule())
val test = MyValueObject("test")
val json = objectMapper.writeValueAsString(test)
println(json)
objectMapper.readValue<MyValueObject>(json)
}
Is there a simple/generic way to deserialize these like @JsonValue
for serializing?
P.S.: One working solution (thanks to @LppEdd) is:
data class MyValueObject (@JsonValue val value: String) {
companion object {
@JvmStatic
@JsonCreator
fun create(value: String) = MyValueObject(value)
}
}
But this is very verbose. @JsonCreator annotated on the constructor did not work for me (see my comment on the answer of @LppEdd)
Jackson uses default (no argument) constructor to create object and then sets value using setters. so you only need @NoArgsConstructor and @Setter.
Jackson is a powerful and efficient Java library that handles the serialization and deserialization of Java objects and their JSON representations.
Note that Jackson does not use java.
Sure, and it's pretty simple.
Just add @JsonCreator
to the constructor.
class MyValueObject {
private String value;
@JsonCreator
public MyValueObject(String value) { this.value = value; }
@JsonValue
public String getValue() { return value; }
}
Or for Kotlin you'd have, I suppose
data class MyValueObject @JsonCreator constructor(@JsonValue val value: String)
When you use @JsonCreator
without prefixing the constructor parameter with
@JsonProperty("fieldName")
you tell Jackson to pass the entire JSON string, which in your case is just a "primitive" value.
Apparently Kotlin doesn't like auto-generated getter/setters pairs.
This works for example
class MyValueObject @JsonCreator constructor(private val value: String) {
@JsonValue
fun getValue() = value
}
Also, after a bit of debugging, I found out the problem arises here
@Override
public Boolean hasAsValue(Annotated a) {
JsonValue ann = _findAnnotation(a, JsonValue.class);
if (ann == null) {
return null;
}
return ann.value();
}
At JacksonAnnotationIntrospector
.
I don't know why but Jackson still look for a @JsonValue
annotation on a public field or on a public getter. Kotlin places the annotation on the private field, so Jackson cannot find it.
The solution is
data class MyValueObject @JsonCreator constructor(@JvmField @JsonValue val value: String)
or even better
data class MyValueObject @JsonCreator constructor(@get:JsonValue val value: String)
As Marc von Renteln wrote in the comments, you can also omit @JsonCreator
data class MyValueObject(@get:JsonValue val value: String)
Which seems undocumented, however. If someone can point out where this behavior is described, it would be awesome!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With