首页 > 解决方案 > 使用惰性 val 序列化案例类会导致 StackOverflow

问题描述

假设我定义了以下案例类:

case class C(i: Int) {
    lazy val incremented = copy(i = i + 1)
}

然后尝试将其序列化为 json:

val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
val out = new StringWriter
mapper.writeValue(out, C(4))
val json = out.toString()
println("Json is: " + json)

它将抛出以下异常:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]->C["incremented"]-
...

我不知道为什么它首先默认尝试序列化惰性val?在我看来,这不是合乎逻辑的方法

我可以禁用此功能吗?

标签: scalajackson-databindakka-clusterjson4s

解决方案


这是因为 Jackson 是为 Java 设计的。具体来说,请注意:

  • Java 不知道lazy val
  • Java 围绕字段和构造函数的正常语义不允许将字段划分为“构造所需”和“构造派生”(这两个都不是技术术语),Scalaval在默认构造函数中的组合(隐式存在于 a 中case class)和val在类的主体中提供

第二种的结果是(有时除了 bean),面向 Java 的序列化方法倾向于假设对象中的任何字段(包括private字段,因为 Java 习惯用法是private默认生成字段)都需要序列化,可以通过@transient注释选择退出。

反过来,第一个意味着lazy vals 由编译器以包含private字段的方式实现。

因此,对于像 Jackson 这样的面向 Java 的序列化器,lazy val没有@transient注释的 a 会被序列化。

面向 Scala 的序列化方法(例如 circe、play-json 等)倾向于case class通过仅序列化构造函数参数来序列化 es。


推荐阅读