Efficiently serializing case classes

谁说我不能喝 提交于 2019-12-08 16:51:29

问题


For a library I'm working on, I need to provide an efficient, convenient and typesafe method of serializing scala classes. The ideal would be if a user can create a case class, and as long as all the members are serializable it should seemlessly be so too. I know precisely the type during both the serializing and deserializing stage so there's no need (and can not afford to) have any "schema" information as part of the seriazation format (like the Java object serialisation).

I've been playing with a few ideas, and this one seems to come pretty close. The major problem I see here, is how the user has to specify class's "apply" and "unapply" function. Since these are really static functions, I'm wondering if it's possible to get the compiler to find it.

Here's a self contained example:

trait InOut[T] {
  // just keeping things simple, for illustration purposes
  def toWire(x: T): Array[Byte]
  def fromWire(v: Array[Byte] ): T
}

object InOutConversions {
  // Pretend these are implemented properly

    implicit def Int = new InOut[Int] {
      def toWire(x: Int): Array[Byte] = Array[Byte]()
      def fromWire(v: Array[Byte] ): Int = 44
    }

    implicit def String = new InOut[String] {
      def toWire(x: String): Array[Byte] = Array[Byte]()
      def fromWire(v: Array[Byte] ): String = "blah"
    }

    // etc... for all the basic types
}

And then I need a function like this:

def serialize2[T, A1 : InOut, A2 : InOut](unapply : T => Option[Product2[A1, A2]])(obj : T) : Array[Byte] = {
  val product : Product2[A1, A2] = unapply(obj).get 
   implicitly[InOut[A1]].toWire(product._1) ++ implicitly[InOut[A2]].toWire(product._2)
}

Which would allow a user to use it pretty easy. e.g.

case class Jesus(a: Int, b: String)
val j = Jesus(4, "Testing")
serialize2 (Jesus.unapply(_)) (j)

But as you can see, that last line was really quite yuck. Surely it must be possible to improve on that? (Given a Jesus, surely I can find the 'unapply' static method)


回答1:


Because don't have a generic way of getting the companion object of a given case class, you will add to do some extra work. I see three options:

  1. You can either use structural typing, but you will loose performance
  2. You can use a small type class to resolve implicitly the correct unapply method.
  3. You can add a toTuple method to the case class instances.

For instance:

trait ToTuple2[A1,A2] {
  def toTuple: (A1,A2)
}

case class Jesus(a: Int, b: String) extends ToTuple2[Int,String] {
  val toTuple = (a,b)
}

def serialize2[T <: ToTuple2[A1,A2], A1 : InOut, A2 : InOut](obj : T): Array[Byte] = {
  val product : Product2[A1, A2] = obj.toTuple
  implicitly[InOut[A1]].toWire(product._1) ++ implicitly[InOut[A2]].toWire(product._2)
}

Code example for option 2:

case class Jesus(a: Int, b: String)

trait Unapply2[T,A1,A2] {
  def asTuple( t: T ): (A1,A2)
}

implicit val UnapJesus = new Unapply2[Jesus,Int,String] {
  def asTuple( j: Jesus ) = Jesus.unapply(j).get
}

def serialize2[T, A1, A2](obj : T)
(implicit unap: Unapply2[T,A1,A2], inout1: InOut[A1], inout2: InOut[A2])  : Array[Byte] = {
  val product : Product2[A1, A2] = unap.asTuple(obj)
  inout1.toWire(product._1) ++ inout2.toWire(product._2)
}

You should have a look at SBinary, it looks similar.



来源:https://stackoverflow.com/questions/7749316/efficiently-serializing-case-classes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!