§JSON の基本
近年の Web アプリケーションでは、JSON (JavaScript Object Notation) 形式のデータを解析・生成する必要があります。Play はこれを JSON ライブラリ でサポートしています。
JSON は軽量のデータ交換フォーマットで、次のようなものです。
{
"name" : "Watership Down",
"location" : {
"lat" : 51.235685,
"long" : -1.309197
},
"residents" : [ {
"name" : "Fiver",
"age" : 4,
"role" : null
}, {
"name" : "Bigwig",
"age" : 6,
"role" : "Owsla"
} ]
}
JSON の詳細については、json.org を参照してください。
§Play JSON ライブラリ
play.api.libs.json
パッケージには、JSON データを表現するためのデータ構造と、これらのデータ構造と他のデータ表現との間で変換を行うためのユーティリティが含まれています。重要な型は次のとおりです。
§JsValue
これは JSON の値を表すトレイトです。JSON ライブラリには以下のような、JsValue
を拡張した有効な JSON 型を表すケースクラスがあります。
さまざまな JsValue
型を使用して、任意の JSON 構造の表現を構築することができます。
§Json
Json
オブジェクトは、主に JsValue
構造との変換に用いるユーティリティを提供します。
§JsPath
XML に対する XPath のような、JsValue
構造へのパスを表します。これは、JsValue
構造を走査するために使うものであり、暗黙のコンバータパターン内にて使われています。
§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"
} ]
}
""")
§クラス構築の使用
import play.api.libs.json._
val json: JsValue = JsObject(Seq(
"name" -> JsString("Watership Down"),
"location" -> JsObject(Seq("lat" -> JsNumber(51.235685), "long" -> JsNumber(-1.309197))),
"residents" -> JsArray(Seq(
JsObject(Seq(
"name" -> JsString("Fiver"),
"age" -> JsNumber(4),
"role" -> JsNull
)),
JsObject(Seq(
"name" -> JsString("Bigwig"),
"age" -> JsNumber(6),
"role" -> JsString("Owsla")
))
))
))
Json.obj
と Json.arr
は構築を少しだけ簡単にできます。ほとんどの値は JsValue クラスで明示的にラップする必要はないことに注意してください。ファクトリメソッドは暗黙の変換を使用します (詳細は後述) 。
import play.api.libs.json.{JsNull,Json,JsString,JsValue}
val json: JsValue = Json.obj(
"name" -> "Watership Down",
"location" -> Json.obj("lat" -> 51.235685, "long" -> -1.309197),
"residents" -> Json.arr(
Json.obj(
"name" -> "Fiver",
"age" -> 4,
"role" -> JsNull
),
Json.obj(
"name" -> "Bigwig",
"age" -> 6,
"role" -> "Owsla"
)
)
)
§書き込みコンバータの使用
Scala から JsValue
への変換は、ユーティリティメソッド Json.toJson[T](T)(implicit writes: Writes[T])
によって実行されます。この機能は T
を JsValue
に変換する Writes[T]
型のコンバータに依存します。
Play JSON API は、Int
、Double
、String
、Boolean
などのほとんどの基本型に対して暗黙の Writes
を提供します。Writes[T]
が存在するあらゆる T
型について、これらのコレクションの Writes
もサポートしています。
import play.api.libs.json._
// basic types
val jsonString = Json.toJson("Fiver")
val jsonNumber = Json.toJson(4)
val jsonBoolean = Json.toJson(false)
// collections of basic types
val jsonArrayOfInts = Json.toJson(Seq(1, 2, 3, 4))
val jsonArrayOfStrings = Json.toJson(List("Fiver", "Bigwig"))
独自のモデルを JsValue
に変換するには、暗黙の Writes
コンバータを定義し、それらをスコープ内に提供する必要があります。
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])
import play.api.libs.json._
implicit val locationWrites = new Writes[Location] {
def writes(location: Location) = Json.obj(
"lat" -> location.lat,
"long" -> location.long
)
}
implicit val residentWrites = new Writes[Resident] {
def writes(resident: Resident) = Json.obj(
"name" -> resident.name,
"age" -> resident.age,
"role" -> resident.role
)
}
implicit val placeWrites = new Writes[Place] {
def writes(place: Place) = Json.obj(
"name" -> place.name,
"location" -> place.location,
"residents" -> place.residents)
}
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)
代わりに、コンビネータパターンを使って Writes
を定義することもできます。
メモ: コンビネータパターンについては、JSON Reads/Writes/Formats コンビネータ で詳しく説明しています。
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))
§JsValue 構造の走査
JsValue
構造を走査して特定の値を抽出できます。構文と機能は Scala の XML 処理と似ています。
メモ: 次の例は、前の例で作成された JsValue 構造に適用されます。
§基本的なパス \
\
演算子を JsValue
に適用すると、これが JsObject
であると仮定して、フィールド引数に対応するプロパティを返します。
val lat = (json \ "location" \ "lat").get
// returns JsNumber(51.235685)
§再帰的パス \\
\\
演算子を適用すると、現在のオブジェクトとすべての子孫のフィールドを検索します。
val names = json \\ "name"
// returns Seq(JsString("Watership Down"), JsString("Fiver"), JsString("Bigwig"))
§インデックス検索 (JsArrays の場合)
インデックス番号付きの apply 演算子を使用して、JsArray
の中の値を検索できます。
val bigwig = (json \ "residents")(1)
// returns {"name":"Bigwig","age":6,"role":"Owsla"}
§JsValue からの変換
§文字列ユーティリティの使用
縮小版:
val minifiedString: String = Json.stringify(json)
{"name":"Watership Down","location":{"lat":51.235685,"long":-1.309197},"residents":[{"name":"Fiver","age":4,"role":null},{"name":"Bigwig","age":6,"role":"Owsla"}]}
可読版:
val readableString: String = Json.prettyPrint(json)
{
"name" : "Watership Down",
"location" : {
"lat" : 51.235685,
"long" : -1.309197
},
"residents" : [ {
"name" : "Fiver",
"age" : 4,
"role" : null
}, {
"name" : "Bigwig",
"age" : 6,
"role" : "Owsla"
} ]
}
§JsValue.as/asOpt の使用
JsValue
を別の型に変換する最も簡単な方法は、JsValue.as[T](implicit fjs: Reads[T]): T
を使用することです。これには、JsValue
を T
に変換する型 Reads[T]
(Writes[T]
の逆) の暗黙のコンバータが必要です。Writes
と同様に、JSON API は基本的な型の Reads
を提供します。
val name = (json \ "name").as[String]
// "Watership Down"
val names = (json \\ "name").map(_.as[String])
// Seq("Watership Down", "Fiver", "Bigwig")
パスが見つからないか、変換が不可能な場合、as
メソッドは JsResultException
をスローします。より安全なメソッドは、JsValue.asOpt[T](implicit fjs: Reads[T]): Option[T]
です。
val nameOption = (json \ "name").asOpt[String]
// Some("Watership Down")
val bogusOption = (json \ "bogus").asOpt[String]
// None
asOpt
メソッドはより安全ですが、エラー情報は失われます。
§バリデーションの使用
JsValue
から別の型に変換するための好ましい方法は、(Reads
型の引数を取る) validate
メソッドを使うことです。これはバリデーションと変換の両方を実行し、JsResult
の型を返します。JsResult
は2つのクラスによって実装されます。
バリデーション結果を処理するためのさまざまなパターンを適用できます。
val json = { ... }
val nameResult: JsResult[String] = (json \ "name").validate[String]
// Pattern matching
nameResult match {
case s: JsSuccess[String] => println("Name: " + s.get)
case e: JsError => println("Errors: " + JsError.toFlatJson(e).toString())
}
// Fallback value
val nameOrFallback = nameResult.getOrElse("Undefined")
// map
val nameUpperResult: JsResult[String] = nameResult.map(_.toUpperCase())
// fold
val nameOption: Option[String] = nameResult.fold(
invalid = {
fieldErrors => fieldErrors.foreach(x => {
println("field: " + x._1 + ", errors: " + x._2)
})
None
},
valid = {
name => Some(name)
}
)
§JsValue からモデルへ
JsValue からモデルに変換するには、T
がモデルの型である暗黙の Reads[T]
を定義しなければなりません。
メモ:
Reads
と独自のバリデーションの実装に使われるパターンは、JSON Reads/Writes/Formats コンビネータ で詳しく説明しています。
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])
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val locationReads: Reads[Location] = (
(JsPath \ "lat").read[Double] and
(JsPath \ "long").read[Double]
)(Location.apply _)
implicit val residentReads: Reads[Resident] = (
(JsPath \ "name").read[String] and
(JsPath \ "age").read[Int] and
(JsPath \ "role").readNullable[String]
)(Resident.apply _)
implicit val placeReads: Reads[Place] = (
(JsPath \ "name").read[String] and
(JsPath \ "location").read[Location] and
(JsPath \ "residents").read[Seq[Resident]]
)(Place.apply _)
val json = { ... }
val placeResult: JsResult[Place] = json.validate[Place]
// JsSuccess(Place(...),)
val residentResult: JsResult[Resident] = (json \ "residents")(1).validate[Resident]
// JsSuccess(Resident(Bigwig,6,Some(Owsla)),)
Next: JSON と HTTP
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。