Search code examples
scalaserializationjacksondeserializationtransient

How to initialize transient fields during deserialization?


Following code shows serialization and deserialization of simple classes using Jackson. Trouble is during deserialization normal constructor of Root is not called, and therefore the transient fields name of Leaf classes do not have values they had when constructed originally. Is there some way how to provide the transient fields with desired values, without having to make them vars? Some custom serializer or some clever annotations?

I do not want to serialize name values to keep the serialized format as compact as possible - after all the value is given by the data structure and it should be possible to recreate it from the structure again.

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])

}

Solution

  • Handling subobjects as part of the owning object

    Even if Leaf objects are separate JVM objects, from serialization point of view they can be handled as part of the Root object and all the naming can be done by its constructor. For this constructor params need to be annotated and getters created and annotated accessing inner Leaf values, while the Leaf values itself are marked @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
    
    }
    

    Using @JsonDeserialier(as)

    Another option is to use @JsonDeserialier(as=xxx) annotation, then you need to create subclasses for each transient value needed:

    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
    )