Documentation

§JSON Reads/Writes/Format コンビネータ

JSON の基本 では、JsValue 構造体と他のデータ型との変換に使用されるコンバータ ReadsWrites を紹介しました。このページでは、これらのコンバータを構築する方法と、変換中にバリデーションを使用する方法について詳しく説明します。

このページの例では、次のような JsValue 構造体と、対応するモデルを使用します。

import play.api.libs.json._

val json: JsValue = Json.parse("""
{
  "name" : "Watership Down",
  "location" : {
    "lat" : 51.235685,
    "long" : -1.309197
  },
  "residents" : [ {
    "name" : "Fiver",
    "age" : 4,
    "role" : null
  }, {
    "name" : "Bigwig",
    "age" : 6,
    "role" : "Owsla"
  } ]
}
""")
case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])

§JsPath

JsPath は、Reads/Writes を作るための中核となる構成要素です。JsPathJsValue 構造体におけるデータの位置を表します。JsValue を走査する構文と同様のものを使うことにより、(ルートパスの) JsPath オブジェクトを使って JsPath 子インスタンスを定義できます。

import play.api.libs.json._

val json = { ... }

// Simple path
val latPath = JsPath \ "location" \ "lat"

// Recursive path
val namesPath = JsPath \\ "name"

// Indexed path
val firstResidentPath = (JsPath \ "residents")(0)

play.api.libs.json パッケージは、JsPath の別名として __ (ダブルアンダースコア) を定義します。必要に応じてこれを使用できます。

val longPath = __ \ "location" \ "long"

§Reads

Reads コンバータは JsValue から別の型に変換するために使われます。Reads を組み合わせたりネストして、より複雑な Reads を作成することができます。

Reads を作るためにこれらのインポートが必要になります。

import play.api.libs.json._ // JSON library
import play.api.libs.json.Reads._ // Custom validation helpers
import play.api.libs.functional.syntax._ // Combinator syntax

§パス Reads

JsPath には、指定されたパスの JsValue に、別の Reads を適用する特殊な Reads メソッドを作成するメソッドがあります。

メモ: JSON ライブラリは、StringIntDouble などの基本型に対する暗黙の Reads を提供します。

単一のパスを定義する Reads は次のようになります。

val nameReads: Reads[String] = (JsPath \ "name").read[String]

§複雑な Reads

複雑なモデルに変換するために使用できる、より複雑な Reads を形成するために、単一のパス Reads を組み合わせることができます。

分かりやすくするために、組み合わせた機能を 2 つのステートメントに分解します。最初に、and コンビネータを使用して Reads オブジェクトを結合します。

val locationReadsBuilder =
  (JsPath \ "lat").read[Double] and
  (JsPath \ "long").read[Double]

これは FunctionalBuilder[Reads]#CanBuild2[Double, Double] の型を生成します。これは中間のオブジェクトであり、複雑な Reads を作成するために使用されていることだけを知っていれば、あまり心配する必要はありません。

次に、個々の値をモデルに変換する関数を持つ CanBuildXapply メソッドを呼び出すと、複雑な Reads が返ります。コンストラクタのシグネチャが一致するケースクラスがある場合は、単にその apply メソッドを使うことができます。

implicit val locationReads = locationReadsBuilder.apply(Location.apply _)

同じコードを単一のステートメントで書くと以下のようになります。

implicit val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double] and
  (JsPath \ "long").read[Double]
)(Location.apply _)

§Reads のバリデーション

JsValue.validate メソッドは、JsValue から別の型へのバリデーションと変換を行うための良い方法として JSON の基本 で紹介しました。基本的なパターンは次のとおりです。

val json = { ... }

val nameReads: Reads[String] = (JsPath \ "name").read[String]

val nameResult: JsResult[String] = json.validate[String](nameReads)

nameResult match {
  case s: JsSuccess[String] => println("Name: " + s.get)
  case e: JsError => println("Errors: " + JsError.toFlatJson(e).toString())
}

Reads のデフォルトのバリデーションは、型変換エラーをチェックするなどの最小限のものです。Reads バリデーションヘルパーを使って独自のバリデーションルールを定義することができます。よく使われるものを次に示します。

バリデーションを追加するには、JsPath.read メソッドの引数としてヘルパーを適用します。

val improvedNameReads =
  (JsPath \ "name").read[String](minLength[String](2))

§すべてをひとまとめにする

複雑な Reads と独自のバリデーションを使うことで、例題モデルに対して有効な Reads のセットを定義して適用することができます。

import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._

implicit val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double](min(-90.0) keepAnd max(90.0)) and
  (JsPath \ "long").read[Double](min(-180.0) keepAnd max(180.0))
)(Location.apply _)

implicit val residentReads: Reads[Resident] = (
  (JsPath \ "name").read[String](minLength[String](2)) and
  (JsPath \ "age").read[Int](min(0) keepAnd max(150)) and
  (JsPath \ "role").readNullable[String]
)(Resident.apply _)

implicit val placeReads: Reads[Place] = (
  (JsPath \ "name").read[String](minLength[String](2)) and
  (JsPath \ "location").read[Location] and
  (JsPath \ "residents").read[Seq[Resident]]
)(Place.apply _)


val json = { ... }

json.validate[Place] match {
  case s: JsSuccess[Place] => {
    val place: Place = s.get
    // do something with place
  }
  case e: JsError => {
    // error handling flow
  }
}

複雑な Reads はネストすることができます。この場合、placeReads は、あらかじめ定義された暗黙的な locationReadsresidentReads を構造体内の特定のパスで使います。

§Writes

Writes コンバータは、ある型を JsValue に変換するために使われます。

JsPath と、Reads にとてもよく似たコンビネータを使って複雑な Writes を構築することができます。例題モデルの Writes はこのようになります。

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))

implicit val residentWrites: Writes[Resident] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "age").write[Int] and
  (JsPath \ "role").writeNullable[String]
)(unlift(Resident.unapply))

implicit val placeWrites: Writes[Place] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "location").write[Location] and
  (JsPath \ "residents").write[Seq[Resident]]
)(unlift(Place.unapply))


val place = Place(
  "Watership Down",
  Location(51.235685, -1.309197),
  Seq(
    Resident("Fiver", 4, None),
    Resident("Bigwig", 6, Some("Owsla"))
  )
)

val json = Json.toJson(place)

複雑な WritesReads にはいくつかの違いがあります。

§再帰型

例題モデルが示していない特殊なケースの 1 つは、再帰型に対して ReadsWrites を扱う方法です。JsPath は、これを処理するための名前による引数を取る lazyRead メソッドと lazyWrite メソッドを提供します。

case class User(name: String, friends: Seq[User])

implicit lazy val userReads: Reads[User] = (
  (__ \ "name").read[String] and
  (__ \ "friends").lazyRead(Reads.seq[User](userReads))
)(User)

implicit lazy val userWrites: Writes[User] = (
  (__ \ "name").write[String] and
  (__ \ "friends").lazyWrite(Writes.seq[User](userWrites))
)(unlift(User.unapply))

§Format

Format[T]ReadsWrites の特徴をミックスさせただけのものであり、これらのコンポーネントの代わりに暗黙の変換用に使うことができます。

§Reads と Writes からの Format の作成

Format は、同じ型の ReadsWrites から構築することで定義できます。

val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double](min(-90.0) keepAnd max(90.0)) and
  (JsPath \ "long").read[Double](min(-180.0) keepAnd max(180.0))
)(Location.apply _)

val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))

implicit val locationFormat: Format[Location] =
  Format(locationReads, locationWrites)

§コンビネータを使用した Format の作成

ReadsWrites が対称の場合 (実際のアプリケーションではそうでないかもしれません)、コンビネータから直接 Format を定義することができます。

implicit val locationFormat: Format[Location] = (
  (JsPath \ "lat").format[Double](min(-90.0) keepAnd max(90.0)) and
  (JsPath \ "long").format[Double](min(-180.0) keepAnd max(180.0))
)(Location.apply, unlift(Location.unapply))

Next: JSON トランスフォーマ


このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。