Существует ли общий способ десериализации объектов с однозначными значениями (без специального десериализатора) в Джексоне - PullRequest
3 голосов
/ 03 апреля 2019

Мы используем множество объектов с одним значением.Для (де-) сериализации мы используем Джексона с kotlin-модулем.

Пример объекта значения в Kotlin:

data class MyValueObject(val value: String)

или как Java

class MyValueObject {
 private String value;

 public MyValueObject(String value) { this.value = value; }

 public String getValue() { return value; }
}

Этиобъекты значений должны быть сериализованы и десериализованы и должны быть сериализованы «только значение», например."theValue" вместо "{"value":"theValue"}".

Я бы хотел избежать написания пользовательских сериализаторов / десериализаторов для десятков объектов-значений.

Я знаю, что для сериализации @JsonValue может использоваться для реализации вышеупомянутого.

data class MyValueObject(@JsonValue val value: String)

Но JSON "theValue" (вышеприведенный сериализованный String-Literal) не может быть десериализован обратнокак MyValueObject.Это приводит к следующему исключению:

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]

Вот модульный тест, который я использовал:

@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)
}

Есть ли простой / универсальный способ десериализации таких как @JsonValue для сериализации?

PS: Одно из рабочих решений (благодаря @LppEdd):

data class MyValueObject (@JsonValue val value: String) {

    companion object {
        @JvmStatic
        @JsonCreator
        fun create(value: String) = MyValueObject(value)
    }
}

Но это очень многословно.@JsonCreator, аннотированный на конструкторе, у меня не сработал (см. Мой комментарий к ответу @LppEdd)

1 Ответ

3 голосов
/ 03 апреля 2019

Конечно, и это довольно просто.
Просто добавьте @JsonCreator в конструктор.

class MyValueObject {
  private String value;

  @JsonCreator
  public MyValueObject(String value) { this.value = value; }

  @JsonValue
  public String getValue() { return value; }
}

Или для Котлина вы бы, наверное,

data class MyValueObject @JsonCreator constructor(@JsonValue val value: String)

Когда вы используете @JsonCreator без с префиксом параметра конструктора с

@JsonProperty("fieldName")

вы говорите Джексону передать всю строку JSON, которая в вашем случае является просто «примитивным» значением.


Очевидно, что Котлину не нравятся автоматически генерируемые пары геттер / сеттер.
Это работает, например,

class MyValueObject @JsonCreator constructor(private val value: String) {
    @JsonValue
    fun getValue() = value
}

Кроме того, после небольшой отладки я обнаружил, что проблема возникает здесь

@Override
public Boolean hasAsValue(Annotated a) {
    JsonValue ann = _findAnnotation(a, JsonValue.class);
    if (ann == null) {
        return null;
    }
    return ann.value();
}

В JacksonAnnotationIntrospector.

Я не знаю почему, но Джексон все еще ищет аннотацию @JsonValue в общедоступном поле или в общедоступном геттере. Котлин помещает аннотацию в приватное поле, поэтому Джексон не может ее найти.

Решение

data class MyValueObject @JsonCreator constructor(@JvmField @JsonValue val value: String)

или даже лучше

data class MyValueObject @JsonCreator constructor(@get:JsonValue val value: String)

Как писал Марк фон Рентельн в комментариях, вы также можете опустить @JsonCreator

data class MyValueObject(@get:JsonValue val value: String)

Что кажется недокументированным, однако. Если кто-то может указать, где описывается это поведение, это было бы здорово!

...