Utilizamos una gran cantidad de objetos de valor con un solo valor. Para la serialización (des) utilizamos Jackson con el Kotlin-módulo.
Un objeto de valor de ejemplo en Kotlin:
data class MyValueObject(val value: String)
o como Java
class MyValueObject {
private String value;
public MyValueObject(String value) { this.value = value; }
public String getValue() { return value; }
}
Estos objetos de valor tienen que ser serializado y deserializado y deben ser serializado "valor único", por ejemplo. "theValue"
en lugar de "{"value":"theValue"}"
.
Me gustaría evitar escribir serializadores personalizados / deserializadores para objetos de valor de decenas.
Sé que para la serialización @JsonValue
puede ser utilizado para darse cuenta de lo anterior.
data class MyValueObject(@JsonValue val value: String)
Pero el JSON "" theValue "" (lo anterior serializado literal-serie) no puede ser deserializado espalda MyValueObject
. Se sesults en la siguiente excepción:
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]
Aquí está la unidad de prueba que utiliza:
@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)
}
¿Hay una manera sencilla / genérico para deserializar estos como @JsonValue
para serializar?
PD: Una solución de trabajo (gracias a @LppEdd) es:
data class MyValueObject (@JsonValue val value: String) {
companion object {
@JvmStatic
@JsonCreator
fun create(value: String) = MyValueObject(value)
}
}
Pero esto es muy detallado. @JsonCreator anotada sobre el constructor no funcionó para mí (ver mi comentario sobre la respuesta de @LppEdd)
Claro, y es bastante simple.
Sólo tiene que añadir @JsonCreator
al constructor.
class MyValueObject {
private String value;
@JsonCreator
public MyValueObject(String value) { this.value = value; }
@JsonValue
public String getValue() { return value; }
}
O para Kotlin que tendría, supongo
data class MyValueObject @JsonCreator constructor(@JsonValue val value: String)
Cuando se utiliza @JsonCreator
sin prefijar el parámetro constructor
@JsonProperty("fieldName")
Jackson le dice a pasar toda la cadena JSON, que en su caso es sólo un valor de "primitivo".
Al parecer no le gusta Kotlin pares de captadores / definidores generados automáticamente.
Esto funciona, por ejemplo,
class MyValueObject @JsonCreator constructor(private val value: String) {
@JsonValue
fun getValue() = value
}
Además, después de un poco de depuración, descubrí el problema se plantea aquí
@Override
public Boolean hasAsValue(Annotated a) {
JsonValue ann = _findAnnotation(a, JsonValue.class);
if (ann == null) {
return null;
}
return ann.value();
}
Al JacksonAnnotationIntrospector
.
No sé por qué, pero Jackson todavía busco una @JsonValue
anotación en un público campo o en un público comprador. Kotlin coloca la anotación en la privada de campo, por lo que Jackson no puede encontrarlo.
La solucion es
data class MyValueObject @JsonCreator constructor(@JvmField @JsonValue val value: String)
o mejor
data class MyValueObject @JsonCreator constructor(@get:JsonValue val value: String)
Como escribió Marc von Renteln en los comentarios, también se puede omitir @JsonCreator
data class MyValueObject(@get:JsonValue val value: String)
Lo que parece indocumentado, sin embargo. Si alguien puede señalar donde se describe este comportamiento, que sería increíble!