Play!: Body parsing with Jerkson

Maarten Winkels

While playing around with the Play! framework today, I stumbled upon the somewhat clunky JSON integration for reading the body of an HTTP request. The recommended approach of using type safe JSON and browsing the tree while creating your custom object or using a Format seems quite cumbersome for most standard situations that the standard libraries Jerkson and Jackson (included in Play!) handle gracefully. In this blog I will describe an approach that uses a custom BodyParser to come to a simpler solution.

The recommended approach of writing an action that processes JSON is to write a Format for your case class, like this:

case class Person (id: Long, firstName: String, lastName: String)

object Person {

  def all():List[Person] = ...
  def create(p : Person):Long = ...

  import play.api.libs.json._
  implicit object PersonFormat extends Format[Person] {
    def reads(json: JsValue) = Person(
      (json \ "id").as[Long],
      (json \ "firstName").as[String],
      (json \ "lastName").as[String])
    def writes(p: Person) = JsObject(Seq(
      "id" -> JsNumber(p.id),
      "firstName" -> JsString(p.firstName),
      "lastName" -> JsString(p.lastName)))
  }
}

The JSON parser can now use the Format in the Actions like this:

import play.api.mvc._
import play.api.libs.json.Json._

object People extends Controller {

  def list = Action {
    Ok(toJson(Person.all()))
  }

  def save = Action(parse.json) { implicit request =>
    var id = Person.create(request.body.as[Person])
    Ok(toJson(Map("id" -> id)))
  }
}

On line 6 the Format is used implicitly to convert the Person objects to JSON. On line 10 the default JSON parse is used in the action. On line 11 the parsed body is converted to a Person using the Format again implicitly and passed to the rest of the application.

This looks quite ok, but when you have a large number of classes with many fields, writing all the Formats becomes quite cumbersome. Especially considering that the Jerkson library, that is underneath all this, already support straight forward Object to JSON mappings that will suffice in most situations.

For standard situations it would be simpler to not have to write a Format at all. This can be achieved by using a generic body parser like this:

import com.codahale.jerkson.Json

import play.api.Play
import play.api.mvc._
import BodyParsers.parse.DEFAULT_MAX_TEXT_LENGTH
import play.api.libs.iteratee._
import play.api.libs.iteratee.Input._

class JsonObjectParser[A : Manifest] extends BodyParser[A] { 
  def apply(request: RequestHeader): Iteratee[Array[Byte], Either[Result, A]] = {
  	Traversable.takeUpTo[Array[Byte]](DEFAULT_MAX_TEXT_LENGTH).apply(Iteratee.consume[Array[Byte]]().map { bytes =>
    scala.util.control.Exception.allCatch[A].either {
      Json.parse[A](new String(bytes, request.charset.getOrElse("utf-8")))
    }.left.map { e =>
      (Play.maybeApplication.map(_.global.onBadRequest(request, "Invalid Json")).getOrElse(Results.BadRequest), bytes)
    }
  }).flatMap(Iteratee.eofOrElse(Results.EntityTooLarge))
    .flatMap {
      case Left(b) => Done(Left(b), Empty)
      case Right(it) => it.flatMap {
        case Left((r, in)) => Done(Left(r), El(in))
        case Right(a) => Done(Right(a), Empty)
      }
    }
  }
}

(Most of this is copied from the JSON parser in the BodyParsers trait of the Play! framework).
This generic object parser uses jerkson directly to convert the body to a Scala object.

Now the application code can be much simpler:

case class Person (id: Long, firstName: String, lastName: String)

object Person {

  def all():List[Person] = ...
  def create(p : Person):Long = ...

}

import play.api.mvc._
import com.codahale.jerkson.Json

object People extends Controller {

  val personParser = new JsonObjectParser[Person]()

  def list = Action {
    Ok(Json.generate(Person.all()))
  }

  def save = Action(personParser) { implicit request =>
    var id = Person.create(request.body)
    Ok(Json.generate(Map("id" -> id)))
  }
}

Instead of the Play! abstraction, we use Jerkson directly now to write the responses. On line 15 the generic parser is instantiated with a concrete type. On line 21 it is used with the action.

Working with JSON in a large Play! application becomes much easier with this approach!

Comments (3)

  1. Erik Rozendaal - Reply

    July 31, 2012 at 9:55 pm

    Hi Maarten,

    Although the reflection based solution can be very convenient I often find it brittle:

    - You often end up with class cast exception. For example, when running continuous test in sbt (~ test).
    - You have little insight into what data is being serialized and what the actual format is. This is especially troublesome if you have nested fields of types that need special serialization (UUID or your own simple value classes). You'll need to register these with the reflection framework, but you'll often forget, which means you may be stuck with some incomprehensible format (I've seen this while using XStream and java.net.URI, before they added a customer converter).

    Fortunately, with a few helpers (https://github.com/zilverline/event-sourced-blog-example/blob/part-3/app/support/JsonMapping.scala#L58) it becomes relatively straightforward to define formats for case classes inside the companion object:

    implicit val PersonFormat: Format[Person] = objectFormat("id", "firstName", "lastName)(apply)(unapply)

    This has the advantage that you'll get a compile time error if there are any missing Format instances for fields within your person class and also gives more complete control over field names, etc.

    Of course, it is not quite as convenient as reflection, but pretty close. I guess it depends on how important a well defined and stable JSON format is.

    Regards,
    Erik

    • Maarten Winkels - Reply

      July 31, 2012 at 10:21 pm

      Hi Erik,

      Fully agree that for more complex cases, you want more control and insight into the conversion to and from the JSON format. And then compile time errors are very nice.
      For simple cases though, the JSON format is easily determined from the Scala/Java classes and the reflection based convertors usually do the right thing.

      It is good to be able to choose for a simple or complex solution/implementation.

      Thanks for the JsonMapping code!

      Cheers,
      -Maarten

  2. Maha - Reply

    November 1, 2013 at 12:06 pm

    Hi Maarten,

    Thanks for the well explained example. However I have a little disclaimer: when you define your models to wrap mapped json, you are using "case class".
    Are you aware of the 22 arguments limit restriction. Most likely, when you are working with nested json, you will hav large amount of data.
    So, did you came across this exception. In case you did can you please share your best tips to get throught it, thanks.

Add a Comment