【问题标题】:How to initialize transient fields during deserialization?如何在反序列化期间初始化瞬态字段?
【发布时间】:2015-04-18 15:21:40
【问题描述】:

以下代码显示了使用 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])

}

【问题讨论】:

    标签: scala serialization jackson deserialization transient


    【解决方案1】:

    您始终可以使用方法在备用构造函数中命名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
    }
    

    【讨论】:

    • 这听起来很有趣,并且适用于线性叶子命名的简化情况。然而叶子的实际结构更复杂,通过同时设置 a 和 b Option[Leaf] 会更接近它。然后几乎不可能使命名器与根给出的名称保持一致 - 很可能需要与根进行一些协调。
    • 另一个缺点:这似乎不是线程安全的。如果多个Root同时反序列化,命名会出错。
    • 我的意思是你可以使用一种方法来命名叶子。该方法不必是这个namer!而且我想表明,如果需要,您可以将方法命名方法与某种状态联系起来。
    • 我很感激,它回答了所写的问题,但恐怕除非命名方法从被序列化的 Root 对象中获取一些上下文,否则无法完成线程安全或复杂命名。或者你有什么方法可以实现吗?
    【解决方案2】:

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

    即使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
    )
    

    【讨论】:

      【解决方案3】:

      我有一个问题:需要知道叶子名称吗?

      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,所以我不知道该怎么做。但是你需要的是 - 一个Serializer[Root](w: Root) = Serializer[(Int, Int)].write(w.a, w.b) 存储a 和b 的值,以及 - 一个 Deserializer[Root](obj) = Deserializer[(Int, Int)].map((a,b) => new Root(a,b)) 将这些作为 Root 实例。

      你明白我的意思

      【讨论】:

        猜你喜欢
        • 2021-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-06
        • 2015-10-18
        相关资源
        最近更新 更多