Let’s Play! - My Play Framework Learning Journey (5) - Working with Json

Often times RESTful services need to deal with Json format - for example convert Json data in the request body to objects, and convert objects to Json before returning it in response body.

Enable Json Auto Conversion

The Play official documentation has a section on Json, but most of the examples there involves code to explicitly map between object fields and Json attributes. That might be necessary when you need to traverse, manipulate or transform Json data, but in my case I just need to do the Json/object conversions in the simplest manner. In fact one just need to define an implicit play.api.libs.json.Format for the type to be converted, and (as usual) make sure the implicit val is in scope when conversion is needed.

case class Currency(id: String, ...)
object Currency {
  implicit val jsonFormat: Format[Currency] = Json.format[Currency]
}

(Added on 2017-05-23) At the moment, Play’s Json macro performance is not as good as using a manual serializer. In my load testing scenario, there is a ~4x performance difference. If you need high JSON serialization performance, then you should use manual serializer instead:

object Currency {
  implicit val format: Format[Currency] = {
    Format(
      Json.reads[Currency],
      Writes { currency =>
        Json.obj(
          "currCode" -> currency.currCode,
          "abbr" -> currency.abbr,
          "desc" -> currency.desc,
          "blbIsoCurr" -> currency.blbIsoCurr,
          "roundingInd" -> currency.roundingInd
        )
      }
    )
  }
}

When you need to return Json in response body, you can do:

// In Repo:
def findAll: Future[Seq[Currency]] = db.run(tables.currencies.result)
...
// In Controller:
def getAll = {
  Action.async {
    repo.findAll map { result => Ok(Json.toJson(result))}
  }
}

Dealing with Json in Request Body

Play Actions use a BodyParser, which parses the HTTP request body content. When Action.apply() or Action.async() is called, a BodyParser can be specified. If none is specified, the default BodyParser[AnyContent] will be used. If you need to deal with Json format in the request body, you can specify a JsonBodyParser as follows:

Action(parse.json[TypeA]) { request =>
  val objA: TypeA = request.body
  ...
}

or

Action.arsync(parse.json[TypeA]) { request =>
  val objA: TypeA = request.body
  ...
}

Note that “parse” is defined in BodyParser trait, which is mixed into Controller trait.

In testing, you can pass TypeA as the request body directly:

val body: TypeA = TypeA(...)
val futureResult: Future[Result] = controller.methodA.apply(FakeRequest().withBody(body))

Additional References

Go Top
comments powered by Disqus