§JSON の基本
最新のウェブアプリケーションでは、多くの場合、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 データを表すためのデータ構造と、これらのデータ構造と他のデータ表現との間の変換を行うためのユーティリティが含まれています。このパッケージの機能の一部を以下に示します。
- 自動変換:最小限のボイラープレートでケースクラスとの間で自動変換を行います。最小限のコードで迅速に開始したい場合は、おそらくここから始めるのが良いでしょう。
- カスタム検証:解析中のカスタム検証。
- 自動解析:リクエストボディ内の JSON を自動的に解析し、コンテンツが解析できない場合や、正しくない Content-type ヘッダーが提供されている場合は、自動生成されたエラーを返します。
- スタンドアロンライブラリとして、Play アプリケーションの外で使用できます。
libraryDependencies += "org.playframework" %% "play-json" % playVersion
をbuild.sbt
ファイルに追加するだけです。 - 高度にカスタマイズ可能。
このパッケージは次の型を提供します。
§JsValue
これは、任意の JSON 値を表すトレイトです。JSON ライブラリには、有効な JSON 型ごとに表すJsValue
を拡張するケースクラスがあります。
さまざまな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(
IndexedSeq(
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,JsObject}
val json: JsObject = 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"
)
)
)
JsObject
を作成するためにJson.newBuilder
を使用することもできます。
import play.api.libs.json.{ JsNull, Json, JsString, JsObject }
def asJson(active: Boolean): JsObject = {
val builder = Json.newBuilder
builder ++= Seq(
"name" -> "Watership Down",
"location" -> Json.obj(
"lat" -> 51.235685D, "long" -> -1.309197D))
if (active) {
builder += "active" -> true
}
builder += "residents" -> Seq(
Json.obj(
"name" -> "Fiver",
"age" -> 4,
"role" -> JsNull
),
Json.obj(
"name" -> "Bigwig",
"age" -> 6,
"role" -> "Owsla"
))
builder.result()
}
§Writes コンバーターの使用
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
のコレクションもサポートしています。
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: Writes[Location] = new Writes[Location] {
def writes(location: Location) = Json.obj(
"lat" -> location.lat,
"long" -> location.long
)
}
implicit val residentWrites: Writes[Resident] = new Writes[Resident] {
def writes(resident: Resident) = Json.obj(
"name" -> resident.name,
"age" -> resident.age,
"role" -> resident.role
)
}
implicit val placeWrites: Writes[Place] = 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]
)(l => (l.lat, l.long))
implicit val residentWrites: Writes[Resident] = (
(JsPath \ "name").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "role").writeNullable[String]
)(r => (r.name, r.age, r.role))
implicit val placeWrites: Writes[Place] = (
(JsPath \ "name").write[String] and
(JsPath \ "location").write[Location] and
(JsPath \ "residents").write[Seq[Resident]]
)(p => (p.name, p.location, p.residents))
§JsValue 構造のトラバース
JsValue
構造をトラバースして、特定の値を抽出できます。構文と機能は、Scala XML処理に似ています。
注:次の例は、前の例で作成された JsValue 構造に適用されます。
§単純なパス \
\
演算子をJsValue
に適用すると、JsObject
のフィールド引数に対応するプロパティ、またはJsArray
のそのインデックスにあるアイテムが返されます。
val lat = (json \ "location" \ "lat").toOption
// returns some JsNumber(51.235685)
val bigwig = (json \ "residents" \ 1).toOption
// returns some {"name":"Bigwig","age":6,"role":"Owsla"}
\
演算子はJsLookupResult
を返し、これはJsDefined
またはJsUndefined
のいずれかです。複数の\
演算子をチェーンできます。中間値が見つからない場合は、結果はJsUndefined
になります。JsLookupResult
のget
を呼び出すと、定義されている場合は値を取得しようとしますが、定義されていない場合は例外をスローします。
オブジェクトのフィールドまたは配列のインデックスを取得するために、直接ルックアップapply
メソッド(下記)を使用することもできます。get
と同様に、このメソッドは値が存在しない場合は例外をスローします。
§再帰的パス \\
\\
演算子を適用すると、現在のオブジェクトとそのすべての後続要素でフィールドのルックアップが行われます。
val names = json \\ "name"
// returns Seq(JsString("Watership Down"), JsString("Fiver"), JsString("Bigwig"))
§直接ルックアップ
.apply
演算子を使用して、JsArray
またはJsObject
の値を取得できます。これは、単純なパス\
演算子と同一ですが、値を直接返します(JsLookupResult
でラップするのではなく)、インデックスまたはキーが見つからない場合は例外をスローします。
val name = json("name")
// returns JsString("Watership Down")
val bigwig2 = json("residents")(1)
// returns {"name":"Bigwig","age":6,"role":"Owsla"}
// (json("residents")(3)
// throws an IndexOutOfBoundsException
// json("bogus")
// throws a NoSuchElementException
これは、一発限りのスクリプトやREPLなど、存在することがわかっているJSON値にアクセスする迅速で簡単なコードを作成する場合に便利です。
§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
を別の型に変換する推奨される方法は、そのvalidate
メソッドを使用することです(Reads
型の引数を取ります)。これにより、検証と変換の両方が実行され、JsResult
型が返されます。JsResult
は2つのクラスによって実装されます。
検証結果を処理するために、さまざまなパターンを適用できます。
val json = { ... }
val nameResult: JsResult[String] = (json \ "name").validate[String]
// Pattern matching
nameResult match {
case JsSuccess(name, _) => println(s"Name: $name")
case e: JsError => println(s"Errors: ${JsError.toJson(e)}")
}
// 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(s"field: ${x._1}, errors: ${x._2}")
}
Option.empty[String]
},
valid = Some(_)
)
§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)),)
§単純なタプルの使用
単純なJSONオブジェクトは、単純なタプルとして読み書きできます。
import play.api.libs.json._
val tuple3Reads: Reads[(String, Int, Boolean)] =
Reads.tuple3[String, Int, Boolean]("name", "age", "isStudent")
val tuple3Writes: OWrites[(String, Int, Boolean)] =
OWrites.tuple3[String, Int, Boolean]("name", "age", "isStudent")
val tuple3ExampleJson: JsObject =
Json.obj("name" -> "Bob", "age" -> 30, "isStudent" -> false)
val tuple3Example = Tuple3("Bob", 30, false)
tuple3Writes.writes(tuple3Example) mustEqual tuple3ExampleJson
tuple3Reads.reads(tuple3ExampleJson) mustEqual JsSuccess(tuple3Example)
次へ:HTTP と JSON
このドキュメントに誤りを見つけましたか? このページのソースコードはこちらにあります。ドキュメントガイドラインをお読みになった後、プルリクエストにご自由に貢献ください。ご質問やアドバイスがありましたら、コミュニティフォーラムでコミュニティとの会話を始めてください。