Custom type serialization

So far, we have only tried to serialize and deserialize simple types. What if we wanted to decode the language field in the repository array to an enumeration rather than a string? We might, for instance, define the following enumeration:

scala> object Language extends Enumeration {
  val Scala, Java, JavaScript = Value
}
defined object Language

Casbah lets us define custom serializers tied to a specific Scala type: we can inform Casbah that whenever it encounters an instance of the Language.Value type in a DBObject, the instance should be passed through a custom transformer that will convert it to, for instance, a string, before writing it to the database.

To define a custom serializer, we need to define a class that extends the Transformer trait. This trait exposes a single method, transform(o:AnyRef):AnyRef. Let's define a LanguageTransformer trait that transforms from Language.Value to String:

scala> import org.bson.{BSON, Transformer}
import org.bson.{BSON, Transformer}

scala> trait LanguageTransformer extends Transformer {
  def transform(o:AnyRef):AnyRef = o match {
    case l:Language.Value => l.toString
    case _ => o
  }
}
defined trait LanguageTransformer

We now need to register the trait to be used whenever an instance of type Language.Value needs to be decoded. We can do this using the addEncodingHook method:

scala> BSON.addEncodingHook(
  classOf[Language.Value], new LanguageTransformer {})

We can now construct DBObject instances containing values of the Language enumeration:

scala> val repoObj = DBObject(
  "github_id" -> 1234L,
  "language" -> Language.Scala
)
repoObj: DBObject = { "github_id" : 1234 , "language" : "Scala"}

What about the reverse? How do we tell Casbah to read the "language" field as Language.Value? This is not possible with custom deserializers: "Scala" is now stored as a string in the database. Thus, when it comes to deserialization, "Scala" is no different from, say, "mojombo". We thus lose type information when "Scala" is serialized.

Thus, while custom encoding hooks are useful for serialization, they are much less useful when deserializing. A cleaner, more consistent alternative to customize both serialization and deserialization is to use type classes. We have already covered how to use these extensively in Chapter 5, Scala and SQL through JDBC, in the context of serializing to and from SQL. The procedure here would be very similar:

  1. Define a MongoReader[T] type class with a read(v:Any):T method.
  2. Define concrete implementations of MongoReader in the MongoReader companion object for all types of interest, such as String, Language.Value.
  3. Enrich DBObject with a read[T:MongoReader] method using the pimp my library pattern.

For instance, the implementation of MongoReader for Language.Value would be as follows:

implicit object LanguageReader extends MongoReader[Language.Value] {
  def read(v:Any):Language.Value = v match {
    case s:String => Language.withName(s)
  }
}

We could then do the same with a MongoWriter type class. Using type classes is an idiomatic and extensible approach to custom serialization and deserialization.

We provide a complete example of type classes in the code examples associated with this chapter (in the typeclass directory).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset