【问题标题】:Is there a generic way to deserialize single-valued value objects (without custom Deserializer) in Jackson是否有一种通用的方法来反序列化杰克逊中的单值值对象(没有自定义反序列化器)
【发布时间】:2019-08-24 20:02:39
【问题描述】:

我们使用大量具有单个值的值对象。对于(反)序列化,我们使用带有 kotlin 模块的 Jackson。

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""(上述序列化的字符串文字)无法反序列化为 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 以进行序列化?

P.S.:一种可行的解决方案(感谢@LppEdd)是:

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

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

但这非常冗长。在构造函数上注释的@JsonCreator 对我不起作用(请参阅我对@LppEdd 答案的评论)

【问题讨论】:

  • 那么“theValue”是一个简单的字符串?
  • 是的,它是一个字符串。
  • @MarcvonRenteln 查看答案。 JsonCreator 也适用于每种原始类型(字符串和数字)。

标签: java serialization kotlin jackson deserialization


【解决方案1】:

当然,这很简单。
只需将@JsonCreator 添加到构造函数即可。

class MyValueObject {
  private String value;

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

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

或者对于 Kotlin 你应该有,我想

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

当你使用@JsonCreator没有在构造函数参数前加上

@JsonProperty("fieldName")

您告诉 Jackson 传递整个 JSON 字符串,在您的情况下这只是一个“原始”值。


显然 Kotlin 不喜欢自动生成的 getter/setter 对。
这适用于例如

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

我不知道为什么,但 Jackson 仍然在 public 字段或 public getter 上寻找 @JsonValue 注释。 Kotlin 将注释放在 private 字段中,因此 Jackson 找不到它。

解决办法是

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

甚至更好

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

正如 Marc von Renteln 在 cmets 中所写,您也可以省略 @JsonCreator

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

然而,这似乎没有记录。如果有人能指出这种行为是在哪里描述的,那就太棒了!

【讨论】:

  • 抱歉,@JsonCreator 不适合我。当我反序列化 class MyValueObject @JsonCreator constructor(@JsonValue val value: String) 时,它会导致相同的异常(我将其添加到描述中)。我已将我的测试方法添加到问题中。当我将 kotlin 数据类反编译为 Java 时,构造函数如下所示:@JsonCreator public MyValueObject(@NotNull String value) { Intrinsics.checkParameterIsNotNull(value, "value"); super(); this.value = value; } 这似乎是正确的。但是杰克逊不使用它。
  • @MarcvonRenteln 很奇怪。我已经测试了我发布的内容。让我试试。
  • @MarcvonRenteln 好的,这是 Kotlin 的问题。我会看看我能做什么
  • 聊天链接在 Firefox 和 chrome 中对我不起作用。 :(我分析了字节码,修改了你的第一个想法,同时我找到了与你上一个建议完全相同的解决方案@get: ;) 太好了!感谢您的帮助!
  • 通常会跳过这一部分,但这次我不得不说:谢谢@LppEdd
猜你喜欢
  • 1970-01-01
  • 2014-01-31
  • 2013-08-11
  • 1970-01-01
  • 1970-01-01
  • 2014-10-12
  • 1970-01-01
  • 1970-01-01
  • 2021-01-26
相关资源
最近更新 更多