ユーザ情報をJSONで受け取り、登録もしくは更新を行います。

Readsの定義

JsonControllerのコンパニオンオブジェクトにユーザ情報を受け取るためのケースクラスと、JSONからそのケースクラスに変換するためのReadsを定義します。

object JsonController {
  ...

  // ユーザ情報を受け取るためのケースクラス
  case class UserForm(id: Option[Long], name: String, companyId: Option[Int])

  // JSONをUserFormに変換するためのReadsを定義
  implicit val userFormFormat = (
    (__ \ "id"       ).readNullable[Long] and
    (__ \ "name"     ).read[String]       and
    (__ \ "companyId").readNullable[Int]
  )(UserForm)
}

前述のWritesと同様、DSLを使わずに以下のように記述することもできます。

implicit val userFormFormat = new Reads[UserForm]{
  def reads(js: JsValue): UserForm = {
    UserForm(
      id        = (js \ "id"       ).asOpt[Long],
      name      = (js \ "name"     ).as[String],
      companyId = (js \ "companyId").asOpt[Int]
    )
  }
}

ReadsWritesは上記のように明示的にマッピングを定義する方法に加え、以下のようにマクロを使ってシンプルに記述することもできます(Json.readsJson.writesはコンパイル時に上記のようなマッピングを自動生成してくれるマクロです)。

implicit val userFormReads  = Json.reads[UserForm]
implicit val userFormWrites = Json.writes[UserForm]

また、ReadsWritesの両方が必要な場合はJson.formatマクロを使うことができます。Formatを定義しておくとReadsWritesの両方を定義したのと同じ意味になります。

implicit val userFormFormat = Json.format[UserForm]

コントローラ

JsonControllercreateメソッドを以下のように実装します。

JSONリクエストを受け取る場合は

  • Action.async(parse.json) { ... }

のようにアクションにparse.jsonを指定します。rs.body.validateメソッドでJSONをケースクラスに変換でき、変換に失敗した場合の処理をrecoverTotalメソッドで行うことができます。

def create = Action.async(parse.json) { implicit rs =>
  rs.body.validate[UserForm].map { form =>
    // OKの場合はユーザを登録
    val user = UsersRow(0, form.name, form.companyId)
    db.run(Users += user).map { _ =>
      Ok(Json.obj("result" -> "success"))
    }
  }.recoverTotal { e =>
    // NGの場合はバリデーションエラーを返す
    Future {
      BadRequest(Json.obj("result" ->"failure", "error" -> JsError.toJson(e)))
    }
  }
}

同様にupdateメソッドを以下のように実装します。

def update = Action.async(parse.json) { implicit rs =>
  rs.body.validate[UserForm].map { form =>
    // OKの場合はユーザ情報を更新
    val user = UsersRow(form.id.get, form.name, form.companyId)
    db.run(Users.filter(t => t.id === user.id.bind).update(user)).map { _ =>
      Ok(Json.obj("result" -> "success"))
    }
  }.recoverTotal { e =>
    // NGの場合はバリデーションエラーを返す
    Future {
      BadRequest(Json.obj("result" ->"failure", "error" -> JsError.toJson(e)))
    }
  }
}

POINT

  • parse.jsonはボディパーサと呼ばれるもので、リクエストボディの処理方法を決めるものです

実行

コマンドラインから以下のコマンドを実行してユーザ情報を登録・更新できることを確認しましょう。

登録:

curl -H "Content-type: application/json" -XPOST -d '{"name":"TestUser", "companyId":1}' http://localhost:9000/json/create

更新:

curl -H "Content-type: application/json" -XPOST -d '{"id":1, "name":"TestUser", "companyId":1}' http://localhost:9000/json/update

いずれの場合も成功すると以下のJSONが返却されます。

{"result":"success"}

エラー時のレスポンスを確認するために、以下のように不正なJSONを送信してみましょう(プロパティ名がnameではなくuserNameになっている)。

curl -H "Content-type: application/json" -XPOST -d '{"userName":"TestUser"}' http://localhost:9000/json/create

すると以下のようにエラー情報を含むJSONが返却されます。

{"result":"failure","error":{"obj.name":[{"msg":"error.path.missing","args":[]}]}}