Play!: Body parsing with Jerkson

22 Jul, 2012
Xebia Background Header Wave

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: [scala] 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(, "firstName" -> JsString(p.firstName), "lastName" -> JsString(p.lastName))) } } [/scala] The JSON parser can now use the Format in the Actions like this: [scala] 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([Person]) Ok(toJson(Map("id" -> id))) } } [/scala] 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: [scala] import com.codahale.jerkson.Json import play.api.Play import play.api.mvc. import BodyParsers.parse.DEFAULT_MAX_TEXTLENGTH 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.takeUpToArray[Byte].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"))) } { e => (, "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) } } } } [/scala] (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: [scala] 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))) } } [/scala] 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!


Get in touch with us to learn more about the subject and related solutions

Explore related posts