如何在反序列化期间初始化瞬态字段?

How to initialize transient fields during deserialization?

以下代码显示了使用 Jackson 对简单 类 进行序列化和反序列化。问题是在反序列化期间 Root 的正常构造函数未被调用,因此 Leaf 类 的瞬态字段 name 没有最初构造时的值。有什么方法可以为瞬态字段提供所需的值,而不必将它们设为变量?一些自定义序列化程序或一些巧妙的注释?

我不想序列化 name 值以保持序列化格式尽可能紧凑 - 毕竟值是由数据结构给出的,应该可以从结构中重新创建它。

import com.fasterxml.jackson.annotation._
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper

class Leaf(val value:Int, @transient val name:String) {

  def this(@JsonProperty value:Int) = this(value,"")
}

class Root(val a: Leaf, val b:Leaf)

object Main extends App {
  val om = new ObjectMapper() with ScalaObjectMapper {
    registerModule(new DefaultScalaModule)
  }

  val root = new Root(new Leaf(1,"a"), new Leaf(2, "b"))

  val out = om.writeValueAsString(root)

  println(out)

  val test = om.readValue(out, classOf[Root])

}

您始终可以使用一种方法来命名备用构造函数中的 Leaf 个实例。如有必要,此方法甚至可以保留状态。例如,以下将交替调用每个叶子 ab

class Leaf(val value: Int, @transient val name: String) {
    def this(@JsonProperty value:Int) = { 
        this(value, Leaf.namer.next)
    }
}

object Leaf {
    private val namer = Iterator.continually(Seq("a", "b")).flatten
}

将子对象作为拥有对象的一部分进行处理

即使 Leaf 对象是单独的 JVM 对象,从序列化的角度来看,它们也可以作为 Root 对象的一部分进行处理,并且所有命名都可以由其构造函数完成。对于此构造函数参数,需要注释并创建吸气剂并注释访问内部 Leaf 值,而 Leaf 值本身标记为 @transient:

class Leaf(val value:Int, val name:String)

class Root(
  @(JsonProperty @param)("a") aVal: Int,
  @(JsonProperty @param)("b") bVal:Int
) {
  @transient val a = new Leaf(aVal,"a")
  @transient val b = new Leaf(bVal,"b")

  @(JsonProperty @getter) def getA = a.value
  @(JsonProperty @getter) def getB = b.value

}

使用@JsonDeserialier(as)

另一种选择是使用@JsonDeserialier(as=xxx)注解,然后你需要为每个需要的瞬态值创建子类:

class LeafA(value:Int) extends Leaf(value,"a")
class LeafB(value:Int) extends Leaf(value,"b")

case class Root(
  @JsonDeserialize(as=classOf[LeafA]) a: Leaf,
  @JsonDeserialize(as=classOf[LeafB]) b: Leaf
)

我会有一个疑问:什么需要知道叶名?

1) 仅以你为例,没有人。或者可能是 Root,但它已经知道哪个叶子是 a 或 b,因为它是变量的名称。在这种情况下,只需从 Leaf 中删除名称,任何需要获取名称的方法都应该从 Root 对象中获取。例如:

case class Leaf(value: Int)

class Root(val a: Leaf, val b: Leaf){
  def getLeavesWithName(): ((Leaf, String), (Leaf, String)) =
    ((a, "a"), (b,"b"))

这可以通过在 Root 中实施合适的命名规则来推广,例如,推广到叶子序列。

2) 由于某些原因,Leaf 可以是两种类型之一,这与其包含的 Root 相关,但可以独立于它进行处理。

在这种情况下,使用简单的 trait/(case)class 层次结构:

trait Leaf{ 
  val value: Int
  def name: String 
}
case class LeafA(value: Int){ def name = "a" }
case class LeafB(value: Int){ def name = "b" }

case class Root(a: LeafA, b: LeafB)

3) Leaf 需要有一个独立于 Root 的名称,但没有关于命名的特定规则:在某些情况下您只需使用 "a" 和 "b",但那可以是任何东西。

在这种情况下,只需将名称保留在 Leaf 序列化中(无 @transient)


现在,无论您对上述内容做出何种选择,如果您的问题是关于优化没有叶子名称或类型的 Root 的序列化,因为 Root 暗示它,那么就不要在 Root 序列化中序列化 Leaf 对象。

我不熟悉您使用的连载API,所以我不知道该怎么做。但你需要的是 - 存储 a 和 b 值的 Serializer[Root](w: Root) = Serializer[(Int, Int)].write(w.a, w.b),以及 - Deserializer[Root](obj) = Deserializer[(Int, Int)].map((a,b) => new Root(a,b)) 将这些作为根实例。

你明白我的意思