Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Traits and serialization/deserialization

Say I have two traits that I would like to mixin to a class. The traits each implement an abstract method that the class needs.

trait Writable {
    def serialize(out: java.io.DataOutput)
}

trait T1 extends Writable

trait A extends T1 {
   val aNum: Int
   abstract override def serialize(out: java.io.DataOutput) = {
       super.serialize(out)
       println("A serialize")
       out.writeInt(aNum)
   }

   def action = println("A action")
}

trait B extends T1 {
   val bNum: Int
   abstract override def serialize(out: java.io.DataOutput) = {
       super.serialize(out)
       println("B serialize")
       out.writeInt(bNum)
   }

   def action = println("B action")
}

abstract class M[CT1 <: T1](val mNum: Int) extends Writable {
   this: M[CT1] with T1 =>
   def serialize(out: java.io.DataOutput) = {
       println("M serialize")
       out.writeInt(mNum)
   }

   def action
}

I can then construct a concrete M with either A or B and serialize:

scala> val m1 = new M[A](10) with A { val aNum = 20 }
m1: M[A] with A = $anon$1@67c1e630

scala> val m2 = new M[B](20) with B { val bNum = 30 }
m2: M[B] with B = $anon$1@c36f58e

scala> val out = new java.io.DataOutputStream(new java.io.ByteArrayOutputStream())
out: java.io.DataOutputStream = java.io.DataOutputStream@87afebf

scala> m1.serialize(out)
M serialize
A serialize

scala> m2.serialize(out)
M serialize
B serialize

Everything works as expected. But how do I deserialize the objects while respecting the type of trait that is mixed into M? I could output the trait's name in the serialize method and then have M's deserialize method dispatch on the name but what if I have classes other than M that A and B can be mixed into? Then, each class would have to duplicate the behavior of M's dispatching deserialize. The problem gets even worse if I have multiple traits that need to get mixed into an object to make it concrete and each has it's own custom serialization/deserialization to do. Anyone tackle an issue like this?

like image 269
AnthonyF Avatar asked Jul 31 '11 18:07

AnthonyF


People also ask

What is the difference between deserialization and serialization?

Serialization is a mechanism of converting the state of an object into a byte stream. Deserialization is the reverse process where the byte stream is used to recreate the actual Java object in memory.

What is serialization and deserialization of data?

Serialization is the process of converting an object into a stream of bytes to store the object or transmit it to memory, a database, or a file. Its main purpose is to save the state of an object in order to be able to recreate it when needed. The reverse process is called deserialization.

Why do we need serialization and deserialization?

Serialization and deserialization work together to transform/recreate data objects to/from a portable format. Serialization enables us to save the state of an object and recreate the object in a new location. Serialization encompasses both the storage of the object and exchange of data.

What is meant by serialization?

To wit, serialization is the process of converting a data object into a byte stream, and saving the state of the object to be stored on a disk or transmitted across a network. This cuts down the storage size needed and makes it easier to transfer information over a network. Serialization Process.


1 Answers

Yes, people have. The way to go is to use typeclass pattern championed by David MacIver's sbinary and Debasish Ghosh's sjson. Debasish's trilogy

  • Scala Implicits : Type Classes Here I Come
  • Refactoring into Scala Type Classes
  • sjson: Now offers Type Class based JSON Serialization in Scala

are especially useful to all intermediate Scala programmers.

Today, many of the libraries are adopting this methodology including mine scalaxb. See

  • typeclass-based XML data binding

I've borrowed the naming idea from Scala Collections' CanBuildFrom and named my typeclasses as follows:

trait CanReadXML[A] {
  def reads(seq: scala.xml.NodeSeq): Either[String, A]
}

trait CanWriteXML[A] {
  def writes(obj: A, namespace: Option[String], elementLabel: Option[String],
      scope: NamespaceBinding, typeAttribute: Boolean): NodeSeq
}

trait XMLFormat[A] extends CanWriteXML[A] with CanReadXML[A]

Edit:

Could you explain to me how the framework chooses between "with A" or "with B"?

Using the typeclass pattern, the libraries mixes in neither A nor B. To take scalaxb for example, it provides a method called scalaxb.fromXML in the package object defined as follows:

def fromXML[A](seq: NodeSeq, stack: List[ElemName] = Nil)
              (implicit format: XMLFormat[A]): A = format.reads(seq, stack) match {
  case Right(a) => a
  case Left(a) => throw new ParserFailure(a)
}

Given that you have XML document, and you want to unmarshal (deserialize) it to ipo.Address object, you would call

scalaxb.fromXML[ipo.Address](<shipTo xmlns="http://www.example.com/IPO">
  <name>Foo</name>
  <street>1537 Paper Street</street>
  <city>Wilmington</city>
</shipTo>)

The Address object stays pure using the typeclass pattern:

case class Address(name: String, street: String, city: String)

How does the compiler know what to do? The magic is the implicit parameter required by fromXML called implicit format: XMLFormat[A]. This requires that you have XMLFormat[Address] available as an implicit value within the scope where scalaxb.fromXML[ipo.Address](...) is called.

This made available in the code generated by scalaxb because it mixes in XMLProtocol into the package object of ipo package. And ipo.XMLProtocol defines

implicit lazy val IpoAddressFormat: scalaxb.XMLFormat[ipo.Address] = new DefaultIpoAddressFormat {}

Edit2:

I think I am starting to understand the actual question. You have an object consisting of trait mixins, and you want to somehow "deserialize" the trait composition on other process. As you wrote, you could include some tag for each traits, and load whatever you can.

Since I've written so far on typeclass pattern, let me continue with the approach. The neat thing about having serialization code outside of the object is that you could actually describe mixin combination of the object. Suppose there are traits

trait Foo { def foo: Int }
trait Bar { def bar: Int }

and you want to describe the mixin as <obj><foo>1</foo><bar>2</bar></obj>. Here's a gist I whipped up. I defined typeclass instance for Foo, Bar, and Foo with Bar, and called

Def.fromXML[Foo with Bar](<obj><foo>1</foo><bar>2</bar></obj>)

which returned

Right(FooWithBar(1, 2))
like image 176
Eugene Yokota Avatar answered Oct 14 '22 15:10

Eugene Yokota