§Play JSON ライブラリの基礎
§概要
JSON を使いたければ、play.api.libs.json
にある JSON ライブラリを基にした Play の型クラスを使うことをおすすめします。
JSON 文字列をパースするために、Play は超高速な Java ベースの JSON ライブラリ、Jerkson を使います。
このアプローチの利点として、Scala ユーザーは Play の JSON サポートがお膳立てする特上の型安全性と関数型の側面を楽しむことができる一方、Java と Scala の両方の Play は同じ基礎となるライブラリ (Jackson) を共有することができます。
§JSON は AST (_Abstract Syntax Tree: 抽象構文木_)
例として、この JSON を見てください:
{
"user": {
"name" : "toto",
"age" : 25,
"email" : "[email protected]",
"isAlive" : true,
"friend" : {
"name" : "tata",
"age" : 20,
"email" : "[email protected]"
}
}
}
これは、以下の二つの仕組みを使った木構造として見ることができます:
- JSON オブジェクト には
name
/value
のペアのセットが含まれます:name
は文字列value
は以下のいずれかです :- 文字列
- 数値
- 他の JSON オブジェクト
- JSON 配列
- true/false
- null
- JSON 配列 は、上述した value の型からなる値の列です。
正確な JSON 標準について詳しく知りたい場合は、json.org へアクセスしてください
§Json データ型
play.api.libs.json
には、これらの構造を反映した 7 つの JSON データ型が含まれています。
§JsObject
標準で説明されている name/value ペアのセットです。例:
{ "name" : "toto", "age" : 45 }
§JsNull
JSON の null
を表現します。
§JsBoolean
true
か false
の値を取る真偽値です。
§JsNumber
JSON は short
, int
, long
, float
, double
そして BigDecimal
を区別しないので、BigDecimal
を含む JsNumber
として表現されます。Play の JSON API は、より正確な型の Scala オブジェクトに変換します。
§JsArray
配列は、あらゆる Json 値の型の列です (同じ型である必要はありません) 。例:
[ "alpha", "beta", true, 123.44, 334]
§JsString
標準的な文字列です。
§その他のデータ型
§JsUndefined
これは JSON 標準の一部ではなく、抽象構文木においてエラーとなったノードを表現するために API が内部でのみ使用します。
§JsValue
この他すべての型の親となる型です。
§JSON を使う
Play の JSON API の入り口となるのは play.api.libs.json.Json
です。このクラスは次のメソッドを提供します:
Json.parse
: 文字列を JsValue にパースしますJson.stringify
: JsValue をコンパクトな文字列にします (改行、インデントなし)Json.prettyPrint
: JsValue を整形された文字列にします (改行、インデントあり)Json.toJson[T](t: T)(implicit writes: Writes[T])
: 解決された暗黙のWrites[T]
を使った Scala オブジェクトからJsValue
への変換を試みますJson.fromJson[T](json: JsValue)(implicit reads: Reads[T])
: 解決された暗黙のReads[T]
を使ったJsValue
から Scala オブジェクトへの変換を試みますJson.obj()
:JsObject
を作成するための簡素化された文法ですJson.arr()
:JsArray
を作成するための簡素化された文法です
§JSON 文字列のパース
どのような JSON 文字列でも JsValue
として簡単にパースすることができます:
import play.api.libs.json._
val json: JsValue = Json.parse("""
{
"user": {
"name" : "toto",
"age" : 25,
"email" : "[email protected]",
"isAlive" : true,
"friend" : {
"name" : "tata",
"age" : 20,
"email" : "[email protected]"
}
}
}
""")
この処理結果と対応付けられた json
は、以下に続くサンプルコードで使用します。
§JSON ディレクトリの構築
§無骨な方法
上記した Json オブジェクトの例は、別の方法でも作成することができます。以下は無骨なアプローチです。
import play.api.libs.json._
JsObject(Seq(
"users" -> JsArray(Seq(
JsObject(Seq(
"name" -> JsString("Bob"),
"age" -> JsNumber(31),
"email" -> JsString("[email protected]")
)),
JsObject(Seq(
"name" -> JsString("Kiki"),
"age" -> JsNumber(25),
"email" -> JsNull
))
))
))
§推奨する方法
Play は JSON を作成する簡素化された文法も提供しています。上記の JsObject は以下を使って構築することができます:
import play.api.libs.json._
Json.obj(
"users" -> Json.arr(
Json.obj(
"name" -> "Bob",
"age" -> 31,
"email" -> "[email protected]"
),
Json.obj(
"name" -> "Kiki",
"age" -> 25,
"email" -> JsNull
)
)
)
§JSON のシリアライズ
JsValue
を JSON 文字列表現にシリアライズするのは簡単です:
val jsonString: String = Json.stringify(json)
§JSON ツリー内パスへのアクセス
JsValue
さえ手に入れば、JSON ツリーの中を探索することができます。この API は、JsValue
を探索することを除けば、Scala で NodeSeq
を使って XML ドキュメントを探索するために提供されている API と似ています。
§シンプルな \
パス
\
メソッドを使ってオブジェクトのプロパティを渡り歩くことができます:
val name: JsValue = json \ "user" \ "name"
name === JsString("toto")
§再帰的な \\
パス
val emails: Seq[JsValue] = json \ "user" \\ "email"
emails === Seq(JsString("[email protected]"), JsString("[email protected]"))
§JsValue から Scala 値への変換
JSON ツリーを渡り歩いていると JsValue
を見つけることができますが、JsValue から Scala の型へ変換したくなるかもしれません。
例えば、JsString
を String
に、または JsNumber
を Long
に変換したくなることでしょう。
§json.as[T]
による安全でない変換
JsValue をもっとも簡単に値に変換するのは、以下のようにして as[T]
メソッドを使う方法です、
val name: String = (json \ "user" \ "name").as[String]
name === "toto"
しかし、このメソッドは安全ではないので、パスが見つからなかったり、変換が不可能だった場合は、エラーを含む JsResultException
がスローされます。
このエラーは、期待しているであろう
path.not.found
を返さないことに注意してください。これは、このドキュメントの後の方で登場する JSON コンビネーターとは異なります。
これは、(json \ "user" \ "nameXXX")
がJsNull
を返却し、暗黙のReads[String]
は検出されたエラーで説明されている通りJsString
を待ち受けていることによるものです。
§Option[T]
による、より安全な変換
asOpt[T]
メソッドは as[T]
と似ていますが、パスが見つからなかったり、変換が不可能だった場合は、例外をスローする代わりに None
を返します:
val maybeName: Option[String] = (json \ "user" \ "name").asOpt[String]
maybeName === Some("toto")
§validate[T]
による、もっとも安全な変換
asOpt[T]
は、より安全ですが、検出されたエラー等を失ってしまいます。
validate[T]
は、JsResult[T]
を返すことで、JsValue
をもっとも安全で堅牢な方法を提供します:
JsResult[T]
は (最初のエラーに留まらずに) 検出されたすべてのエラーを蓄積します。JsResult[T]
は、これを操作、構成するmap
/flatMap
/fold
を提供するモナド構造です。
§JsResult[T]
をひと言で
JsResult[T]
は二つの値を持つことができます:
- 以下を含む
JsSuccess[T](value: T, path: JsPath = JsPath())
:- 変換に成功した場合には
value: T
- ちなみに、
path
は走査された最新のJsPath
を表現するために API が使う内部的なフィールドなので、気にしないでください。
- 変換に成功した場合には
注意 :
JsPath
については後ほど説明しますが、JSON におけるXMLPath
とまったく同じものです。json \ "user" \ "name"
と書く場合、以下のように書くことができます :(JsPath \ "user" \ "name")(json)
user
に続けてname
を検索するJsPath
を作成し、これを与えられたjson
に適用します。
JsError(errors: Seq[(JsPath, Seq[ValidationError])])
:errors
は(JsPath, Seq[ValidationError])
ペアの列です(JsPath, Seq[ValidationError])
ペアは、与えられたJsPath
で検出したひとつ以上のエラーを示します
このため、成功した変換は次のように見えることでしょう:
val jsresult: JsResult[String] = (json \ "user" \ "name").validate[String]
jsresult === JsSuccess("toto")
パスが見つからない場合は、次のようになります:
val nameXXX = (json \ "user" \ "nameXXX").validate[String]
nameXXX === JsError(List((JsPath, List(ValidationError("error.expected.jsstring")))))
map
と flatMap
が提供されているので、内包表記を使って値を簡単に取り出すことができます:
val nameAndEmail: JsResult[(String, String)] = for {
name <- (json \ "user" \ "name").validate[String]
email <- (json \ "user" \ "email").validate[String]
} yield (name, email)
nameAndEmail must_== JsSuccess(("toto", "[email protected]"))
§再帰的なパス \\
の変換
\\
はサブツリーを再帰的に検索し、標準的な Scala 関数のコレクションである、発見した JsValue の Seq[JsValue]
を返します。
val emails: Seq[String] = (json \ "user" \\ "email").map(_.as[String])
emails === Seq("[email protected]", "[email protected]")
§Scala 値から JsValue への変換
Scala から JSON への変換は、ちょうど T
から JsValue
に変換するために利用できる、暗黙的な型クラス Writes[T]
に基づいた Json.toJson[T](implicit writes: Writes[T])
関数によって行われます。
§とてもシンプルな JsValue を作る
import play.api.libs.json._
val jsonNumber = Json.toJson(4)
jsonNumber === JsNumber(4)
Play の JSON API が暗黙の Writes[Int]
を提供するので、このように変換することができます
§Create a JSON array from a Seq[T]
import play.api.libs.json._
val jsonArray = Json.toJson(Seq(1, 2, 3, 4))
jsonArray === Json.arr(1, 2, 3, 4)
Play の JSON API が暗黙の Writes[Seq[Int]]
を提供するので、このように変換することができます
ここで、Seq[Int]
は問題なく Json 配列に変換することができました。しかし、 Seq
が異質な値を含むとすると、より複雑です:
import play.api.libs.json._
val jsonArray = Json.toJson(Seq(1, "Bob", 3, 4))
これをコンパイルしようとすると、次のエラーが発生します:
No Json deserializer found for type Seq[Any]. Try to implement an implicit Writes or Format for this type.
これは 、Seq[Any]
を Json に変換する手段がないためです (Any
は Json でサポートされていないものまで含むことができますよね?)
シンプルな解決方法は、これを Seq[JsValue]
として扱うことです:
import play.api.libs.json._
val jsonArray = Json.toJson(Seq(
Json.toJson(1), Json.toJson("Bob"), Json.toJson(3), Json.toJson(4)
))
jsonArray === Json.arr(1, "Bob", 3, 4)
Play の JSON API が暗黙の Writes[Seq[JsValue]]
を提供するので、このように変換することができます
Next: Json の読み/書き/フォーマットの結合