Writing custom de/serializers for Jerkson

Since I wrote my previous post on how to use Play! Scala with Jerkson, I was asked a number of times how to make use of custom de/serialization for classes. This happens when e.g. the case class you want to de/serialize has a POJO or another complex type that you’d like to have more control over, such as e.g. the BSON ObjectId or Play’s Anorm PK.

Here’s a wrap-up on how you can easily create custom de/serializers and use them within your Play! Scala application. Let’s take the example of the BSON ObjectId, which you will most likely run into if you use MongoDB in the backend.

First, we need to create a Jackon serializer and de-serializer for the org.bson.types.ObjectId type:

import org.codehaus.jackson.map.annotate.JsonCachable
import org.bson.types.ObjectId
import org.codehaus.jackson.map.{DeserializationContext, JsonDeserializer, SerializerProvider, JsonSerializer}
import org.codehaus.jackson.{Version, JsonParser, JsonGenerator}

@JsonCachable
class ObjectIdSerializer extends JsonSerializer[ObjectId] {
  def serialize(id: ObjectId, json: JsonGenerator, provider: SerializerProvider) {
    json.writeString(id.toString)
  }
}

class ObjectIdDeserializer extends JsonDeserializer[ObjectId] {
  def deserialize(jp: JsonParser, context: DeserializationContext) = {
    if (!ObjectId.isValid(jp.getText)) throw context.mappingException("invalid ObjectId " + jp.getText)
    new ObjectId(jp.getText)
  }
}

This is pretty straightforward: when serializing, we just print out the string representation of the ObjectId via toString(), and when deserializing we check if we have a valid ObjectId and then create an instance given the text representation (or throw an exception if we don’t get a valid one).

Now all what is left to do is to register this de/serializer couple. For that purpose we can extend Jerkson’s Json trait that gives us access to the mapper and register a new Jackson SimpleModule:


import org.codehaus.jackson.map.module.SimpleModule

object CustomJson extends com.codahale.jerkson.Json {
  val module = new SimpleModule("CustomJson", Version.unknownVersion())
  module.addSerializer(classOf[ObjectId], new ObjectIdSerializer)
  module.addDeserializer(classOf[ObjectId], new ObjectIdDeserializer)
  mapper.registerModule(module)
}

And that’s all that is needed. If you are using a custom action in Play! in order to generate Json, make sure to update it so that it uses the CustomJson object we just created:


trait JsonAction { self: Controller =>
  def Json(data: AnyRef): Result = new Result() {
    def apply(request: Request, response: Response) {
      val encoding = getEncoding
      setContentTypeIfNotSet(response, "application/json; charset=" + encoding)
      response.out.write(CustomJson.generate(data).getBytes(encoding))
    }
  }
}