Muster by json4s

Fast serialization for classes in scala

Custom serializers

Muster works with a system of type classes and a custom deserializer is an implementation of a muster.input.Consumer[T] trait. Similarly custom serializers are an implementtation of a muster.output.Producer[T] trait.

If you want to learn more about the internals of muster, check out the extending muster doc.

The functionality described on this page is provided by muster-core. You can get muster-core from maven central. Check the releases page for the latest version.

libraryDependencies += "org.json4s" %% "muster-core" % "latest"

Serializers

A deserializer is an implementation of muster.output.Producer[T]

import muster._

trait Producer[T] {
  def produce(value: T, formatter: OutputFormatter[_])
}

In this case the formatter property provides a mechanism for rendering the provided value. As an example lets define a custom serializer for a person case class.

import muster._

case class Address(firstLine: String, secondLine: Option[String], postcode: String, state: String, country: String)
case class Person(id: Int, name: String, addresses: Seq[Address])

implicit object PersonProducer extends Producer[Person] {
  def produce(value: Person, formatter: OutputFormatter[_]) {
    formatter.startObject()

    formatter.startField("id")
    formatter.int(value.id)

    formatter.startField("name")
    formatter.string(person.name)

    val arrProducer = implicitly[Producer[Seq[Address]]]
    arrProducer.produce(value.addresses, formatter)

    formatter.endObject()
  }
}

For this example writing the id and name property are pretty straightforward. And by implicitly resolving the undefined address producer the compiler will generate a version of a Producer[Address] that would do the exact same thing as in the example for the name property.

The macro for the Seq producer generates code that looks like this:

formatter.startArray()
value.addresses foreach (implicitly[Producer[Address]].produce(_, formatter))
formatter.endArray()

Deserializers

A serializer is an implementation of muster.Consumer[T]

import muster._

trait Consumer[S] {
  def consume(node: AstNode[_]): S
}

In this case we get an ast node representation as node and we need to turn it into type S. As an example we'll provide a custom deserializer for the person case class

import muster._

implicit object PersonConsumer extends Consumer[Person] {
  def consume(node: AstNode[_]): Person = node match {
    case obj: ObjectNode => 
      val addressesConsumer = implicitly[Consumer[Seq[Address]]]
      Person(
        obj.readIntField("id"),
        obj.readStringField("name"),
        addressesConsumer.consume(obj.readArrayField("addresses"))
      )
    case n => throw new MappingException(s"Can't convert a ${n.getClass} to a Person")
  }
}

This example makes use of the shortest notation for writing a custom deserializer. First the addresses consumer is implictly resolved, like in the serializer example, this will get a version of Seq[Consumer[Address]] generated by the compiler. Then reading the fields is relatively straight forward.

The code the macro generates for the extraction of the Seq kind of looks like this:

node match {
  case arr: ArrayNode =>
    val addresses = Seq.newBuilder[Address]
    val addressesConsumer = implicitly[Consumer[Address]]
    while(arr.hasNextNode) {
      addresses += addressesConsumer.consume(arr.readObject())
    }
    addresses.result()
  case n => throw new MappingException("Can't read array from ${n.getClass}")
}