Play 2.1 ScalaでJSONを受け取ってJSONを返す
Play for ScalaでJSONを読み書きする方法について書きます。
ドキュメントではReads/Writes/Format combinatorsを定義する方法が書かれています。
val customReads: Reads[(String, Float, List[String])] = (JsPath \ "key1").read[String](email keepAnd minLength(5)) and (JsPath \ "key2").read[Float](min(45)) and (JsPath \ "key3").read[List[String]] tupledhttp://www.playframework.com/documentation/2.1.1/ScalaJsonCombinators
この方法では入れ子構造のJSONをパースするやり方が分かりませんでした。 \\ で複数マッチできるようですが、うまくいかず。
別のページで、Scala 2.10から導入されたマクロを使う方法が書かれています。
case class Person(name: String, age: Int, lovesChocolate: Boolean) implicit val personReads = Json.reads[Person]http://www.playframework.com/documentation/2.1.1/ScalaJsonInception
この方法を使ってみましょう。
おおまかな流れ
今回は下記のようなJSONを受け取るサービスを考えます。
{ "title": "ほげほげのあんけーと", "description": "なんちゃら勉強会について", "questions": [ { "description": "ほげほげセッションはおもしろかったですか?" }, { "description": "ふがふがセッションはおもしろかったですか?" } ] }
このJSONに対応するクラスは下記のようになります。
case class EnqueteDto(title: String, description: Option[String], questions: List[QuestionDto]) case class QuestionDto(description: String)
case classに対応するFormatを定義すればJSONを読み書きできるようになります。
import play.api.mvc._ import play.api.libs.json._ object Application extends Controller { implicit val questionReads = Json.format[QuestionDto] implicit val enqueteReads = Json.format[EnqueteDto] def createEnquete = Action(parse.json) { request => request.body.validate[EnqueteDto].map { dto => // オッケーの場合はIDを返す Ok(Json.obj("id" -> EnqueteService.create(dto))) }.recoverTotal { e => // バリデーションエラーを返す BadRequest(Json.obj("error" -> JsError.toFlatJson(e))) } } }
パターン
ここでは、HTTPやJSON変換のコードはcontrollersに置き、ドメインのコードはmodels/servicesに置くものとします。すると、先ほどのcase classはサービスのインタフェースを表すのでservicesに属するのが自然かと思います。
package services import models._ import play.api.db.slick.Config.driver.simple._ import play.api.db.slick.DB import play.api.Play.current object EnqueteService { case class QuestionDto(description: String) case class EnqueteDto(title: String, description: Option[String], questions: List[QuestionDto]) def create(dto: EnqueteDto): String = DB.withTransaction { implicit session => // Enquetes.insert(...) // Questions.insert(...) "TODO" } }
本稿で説明した内容は下記のようにパターン化できます。