5

I'm trying to write a custom SPickler / Unpickler pair to work around some the current limitations of scala-pickling. The data type I'm trying to pickle is a case class, where some of the fields already have their own SPickler and Unpickler instances. I'd like to use these instances in my custom pickler, but I don't know how.

Here's an example of what I mean:

// Here's a class for which I want a custom SPickler / Unpickler.
// One of its fields can already be pickled, so I'd like to reuse that logic.
case class MyClass[A: SPickler: Unpickler: FastTypeTag](myString: String, a: A)

// Here's my custom pickler.
class MyClassPickler[A: SPickler: Unpickler: FastTypeTag](
  implicit val format: PickleFormat) extends SPickler[MyClass[A]] with Unpickler[MyClass[A]] {
  override def pickle(
    picklee: MyClass[A],
    builder: PBuilder) {
    builder.beginEntry(picklee)

    // Here we save `myString` in some custom way.
    builder.putField(
      "mySpecialPickler",
      b => b.hintTag(FastTypeTag.ScalaString).beginEntry(
        picklee.myString).endEntry())

    // Now we need to save `a`, which has an implicit SPickler.
    // But how do we use it?

    builder.endEntry()
  }

  override def unpickle(
    tag: => FastTypeTag[_],
    reader: PReader): MyClass[A] = {
    reader.beginEntry()

    // First we read the string.
    val myString = reader.readField("mySpecialPickler").unpickle[String]

    // Now we need to read `a`, which has an implicit Unpickler.
    // But how do we use it?
    val a: A = ???

    reader.endEntry()

    MyClass(myString, a)
  }
}

I would really appreciate a working example. Thanks!

emchristiansen
  • 3,550
  • 3
  • 26
  • 40

1 Answers1

5

Here is a working example:

case class MyClass[A](myString: String, a: A)

Note that the type parameter of MyClass does not need context bounds. Only the custom pickler class needs the corresponding implicits:

class MyClassPickler[A](implicit val format: PickleFormat, aTypeTag: FastTypeTag[A],
                                     aPickler: SPickler[A], aUnpickler: Unpickler[A])
  extends SPickler[MyClass[A]] with Unpickler[MyClass[A]] {

  private val stringUnpickler = implicitly[Unpickler[String]]

  override def pickle(picklee: MyClass[A], builder: PBuilder) = {
    builder.beginEntry(picklee)

    builder.putField("myString",
      b => b.hintTag(FastTypeTag.ScalaString).beginEntry(picklee.myString).endEntry()
    )

    builder.putField("a",
      b => {      
        b.hintTag(aTypeTag)
        aPickler.pickle(picklee.a, b)
      }
    )

    builder.endEntry()
  }

  override def unpickle(tag: => FastTypeTag[_], reader: PReader): MyClass[A] = {
    reader.hintTag(FastTypeTag.ScalaString)
    val tag = reader.beginEntry()
    val myStringUnpickled = stringUnpickler.unpickle(tag, reader).asInstanceOf[String]
    reader.endEntry()

    reader.hintTag(aTypeTag)
    val aTag = reader.beginEntry()
    val aUnpickled = aUnpickler.unpickle(aTag, reader).asInstanceOf[A]
    reader.endEntry()

    MyClass(myStringUnpickled, aUnpickled)
  }

}

In addition to the custom pickler class, we also need an implicit def which returns a pickler instance specialized for concrete type arguments:

implicit def myClassPickler[A: SPickler: Unpickler: FastTypeTag](implicit pf: PickleFormat) =
  new MyClassPickler
Philipp Haller
  • 431
  • 2
  • 5
  • This works, but there is a strange asymmetry: In `pickle`, the fields are named, as in `builder.putField("myString", ...)`, but we don't use those names in `unpickle`. Are these names optional, and if so, how can we avoid using them? – emchristiansen Oct 07 '13 at 15:45
  • Some pickle formats like JSON put the field names into the pickle. Even if they're sometimes unused there is no way to avoid using them currently. The API is still evolving slightly, though. – Philipp Haller Oct 08 '13 at 14:34