Documentation

You are viewing the documentation for the 2.1.5 release in the 2.1.x series of releases. The latest stable release series is 2.4.x.

§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 標準について詳しく知りたい場合は、json.org へアクセスしてください

§Json データ型

play.api.libs.json には、前述の構造を厳密に反映した 7 つの JSON データ型が含まれています。

§JsObject

§JsNull

JSON の null を表現します

§JsBoolean

truefalse の値を取る真偽値です

§JsNumber

§JsArray

§JsString

標準的な文字列です。

§JsUndefined

これは JSON 標準の一部ではなく、抽象構文木においてエラーとなったノードを表現するために API が内部でのみ使用します。

§JsValue

上記のすべての型は、総称的な JSON トレイトである JsValue を継承します。

§基本的な JSON API を使うための最小 import

import play.api.libs.json.Json

この import は、もっとも基本的な JSON 機能へのアクセスを提供します:

§JSON 文字列のパース

どのような JSON 文字列でも JsValue として簡単にパースすることができます:

import play.api.libs.json.Json

val json: JsValue = Json.parse("""
{ 
  "user": {
    "name" : "toto",
    "age" : 25,
    "email" : "[email protected]",
    "isAlive" : true,
    "friend" : {
  	  "name" : "tata",
  	  "age" : 20,
  	  "email" : "[email protected]"
    }
  } 
}
""")

このサンプルは、以下すべての例で使用します。

上記にて説明した通り、パースは Jackson によって行われます。

§JSON ディレクトリの構築

§無骨な方法

上記した Json オブジェクトの例は、別の方法でも作成することができます。
以下は無骨なアプローチです。

import play.api.libs.json._

JsObject(
  "users" -> JsArray(
    JsObject(
      "name" -> JsString("Bob") ::
      "age" -> JsNumber(31) ::
      "email" -> JsString("[email protected]") ::
      Nil) ::
    JsObject(
      "name" -> JsString("Kiki") ::
      "age" -> JsNumber(25) ::
      "email" -> JsNull ::
      Nil
    ) :: Nil
  ) :: Nil
)

§推奨する方法

Play は JSON を作成する簡素化された文法を提供しています。
上記の JsObject は以下のようにして構築することができます:

import play.api.libs.json.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 文字列表現にシリアライズするのは簡単です:

import play.api.libs.json.Json

val jsonString: String = Json.stringify(jsValue)

§JSON ツリー内パスへのアクセス

JsValue さえ手に入れば、JSON ツリーの中を探索することができます。
この API は、JsValue を探索することを除けば、Scala で NodeSeq を使って XML ドキュメントを探索するために提供されている API と似ています。

§シンプルな \ パス

// 様々な種類の Json を操作する必要がある場合に備えて、ここでは json パッケージ配下をすべてインポートします
scala> import play.api.libs.json._

scala> val name: JsValue = json \ "user" \ "name"
name: play.api.libs.json.JsValue = "toto"

§再帰的な \\ パス

// サブツリーを再帰的に検索し、見つかったすべての JsValue に対する
// Seq[JsValue] を返します
scala> val emails: Seq[String] = json \ "user" \\ "email"
emails: Seq[play.api.libs.json.JsValue] = List("[email protected]", "[email protected]")

§JsValue から Scala 値への変換

JSON ツリーを渡り歩いていると JsValue を見つけることができますが、JsValue から Scala の型へ変換したくなるかもしれません。
例えば、JsStringString に、または (もし変換できるなら) JsNumberLong に、と言った具合です。

§json.as[T] による安全でない変換

as[T] は、パスにアクセスして要求された型に変換しようとするので、安全ではありません。しかし、パスが見つからなかったり、変換が不可能だった場合は、検出されたエラーを含む実行時例外 JsResultException を生成します。

§成功した場合: パスが見つかり、変換できる

// (変換が可能でパスが見つかった場合) 提供された型に変換された値を返します
scala> val name: String = (json \ "user" \ "name").as[String]
name: String = toto

§失敗した場合: パスが見つからない

scala> val nameXXX: String = (json \ "user" \ "nameXXX").as[String]
play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(validate.error.expected.jsstring,WrappedArray())))))
	at play.api.libs.json.JsValue$$anonfun$4.apply(JsValue.scala:65)
	at play.api.libs.json.JsValue$$anonfun$4.apply(JsValue.scala:65)
	at play.api.libs.json.JsResult$class.fold(JsResult.scala:69)
	at play.api.libs.json.JsError.fold(JsResult.scala:10)
	at play.api.libs.json.JsValue$class.as(JsValue.scala:63)
	at play.api.libs.json.JsUndefined.as(JsValue.scala:96)

このエラーは、期待しているであろう path.not.found を返さないことに注意してください。これは、このドキュメントの後の方で登場する JSON コンビネーターとは異なります。
これは、(json \ "user" \ "nameXXX")JsNull を返却し、暗黙の Reads[String] は検出されたエラーで説明されている通り JsString を待ち受けていることによるものです。

§失敗した場合: 変換できない

scala> val name: Long = (json \ "user" \ "name").as[Long]
play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(validate.error.expected.jsnumber,WrappedArray())))))
	at play.api.libs.json.JsValue$$anonfun$4.apply(JsValue.scala:65)
	at play.api.libs.json.JsValue$$anonfun$4.apply(JsValue.scala:65)
	at play.api.libs.json.JsResult$class.fold(JsResult.scala:69)
	at play.api.libs.json.JsError.fold(JsResult.scala:10)
	at play.api.libs.json.JsValue$class.as(JsValue.scala:63)
	at play.api.libs.json.JsString.as(JsValue.scala:111)

§Option[T] による、より安全な変換

as[T] は即時性はあるものの堅牢ではないので、あらゆる型のエラーが発生した場合に None を返す asOpt[T] が用意されています。

§成功した場合: パスが見つかり、変換できる

scala> val maybeName: Option[String] = (json \ "user" \ "name").asOpt[String]
maybeName: Option[String] = Some(toto)

§失敗した場合: パスが見つからない

scala> val maybeNameXXX: Option[String] = (json \ "user" \ "nameXXX").asOpt[String]
maybeNameXXX: Option[String] = None

§失敗した場合: 変換できない

scala> val maybeNameLong: Option[Long] = (json \ "user" \ "name").asOpt[Long]
maybeNameLong: Option[Long] = None

§validate[T] による、もっとも安全な変換

asOpt[T] は、より安全ですが、検出されたエラー等を失ってしまいます。

validate[T] は、JsResult[T] を返すことで、JsValue をもっとも安全で堅牢な方法を提供します:

§JsResult[T] をひと言で

JsResult[T] は二つの値を持つことができます:

注意 : JsPath については後ほど説明しますが、JSON における XMLPath とまったく同じものです。
json \ "user" \ "name"
と書く場合、以下のように書くことができます :
(JsPath \ "user" \ "name")(json)
user に続けて name を検索する JsPath を作成し、これを与えられた json に適用します。

いくつかの使用例:

scala> import play.api.libs.json._

scala> val jsres: JsResult[String] = JsString("toto").validate[String]
jsres: JsSuccess("toto")

scala> val jsres: JsResult[String] = JsNumber(123).validate[String]
jsres: play.api.libs.json.JsResult[String] = JsError(List((,List(ValidationError(validate.error.expected.jsstring,WrappedArray())))))

jsres.map{ s: String => …}
jsres.flatMap{ s: String => JsSuccess(s) }

jsres.fold( 
  errors: Seq[(JsPath, Seq[ValidationError])] => // エラーを処理して,
  s: String => // 値を処理する 
)

jsres.map( s: String => // manage value )
     .recoverTotal( jserror: JsError => // エラーを処理をしてデフォルト値を返す)

§成功した場合: パスが見つかり、変換できる

scala> val safeName = (json \ "user" \ "name").validate[String]
safeName: play.api.libs.json.JsResult[String] = JsSuccess(toto,) // path は root なので厳密ではありません

§失敗した場合: パスが見つからない

scala> val nameXXX = (json \ "user" \ "nameXXX").validate[String]
nameXXX: play.api.libs.json.JsResult[String] = 
  JsError(List((,List(ValidationError(validate.error.expected.jsstring,WrappedArray())))))

このエラーは、期待しているであろう path.not.found を返さないことに注意してください。これは、このドキュメントの後の方で登場する JSON コンビネーターとは異なります。
これは、(json \ "user" \ "nameXXX")JsNull を返却し、暗黙の Reads[String] は検出されたエラーで説明されている通り JsString を待ち受けていることによるものです。

§失敗した場合: 変換できない

scala> val name = (json \ "user" \ "name").validate[Long]
name: play.api.libs.json.JsResult[Long] = 
  JsError(List((,List(ValidationError(validate.error.expected.jsnumber,WrappedArray())))))

§再帰的なパス \\ の変換

\\ はサブツリーを再帰的に検索し、標準的な Scala 関数のコレクションである、発見した JsValue の Seq[JsValue] を返します。

scala> val emails: Seq[String] = (json \ "user" \\ "email").map(_.as[String])
emails: Seq[String] = List([email protected], [email protected])

§Scala 値から JsValue への変換

Scala から JSON への変換は、ちょうど T から JsValue に変換するために利用できる、暗黙的な型クラス Writes[T] に基づいた Json.toJson[T](implicit writes: Writes[T]) 関数によって行われます。

§とてもシンプルな JsValue を作る

val jsonNumber = Json.toJson(4)
jsonNumber: play.api.libs.json.JsValue = 4

Play の JSON API が暗黙の Writes[Int] を提供するので、このように変換することができます

§Seq[T] から JSON 配列を作る

import play.api.libs.json.Json

val jsonArray = Json.toJson(Seq(1, 2, 3, 4))
jsonArray: play.api.libs.json.JsValue = [1,2,3,4]

Play の JSON API が暗黙の Writes[Seq[Int]] を提供するので、このように変換することができます

ここで、Seq[Int] は問題なく Json 配列に変換することができました。しかし、 Seq が異質な値を含むとすると、より複雑です:

import play.api.libs.json.Json

val jsonArray = Json.toJson(Seq(1, "Bob", 3, 4))
<console>:11: error: No Json deserializer found for type Seq[Any]. Try to implement an implicit Writes or Format for this type.
       val jsonArray = Json.toJson(Seq(1, "Bob", 3, 4))

Seq[Any] を Json に変換する手段がないためエラーになります (Any は Json でサポートされていないものまで含むことができますよね?)

シンプルな解決方法は、これを Seq[JsValue] として扱うことです:

import play.api.libs.json.Json

val jsonArray = Json.toJson(Seq(
  toJson(1), toJson("Bob"), toJson(3), toJson(4)
))

Play の JSON API が暗黙の Writes[Seq[JsValue]] を提供するので、このように変換することができます

§Map[String, T] から JSON オブジェクトを作る

import play.api.libs.json.Json

val jsonObject = Json.toJson(
  Map(
    "users" -> Seq(
      toJson(
        Map(
          "name" -> toJson("Bob"),
          "age" -> toJson(31),
          "email" -> toJson("[email protected]")
        )
      ),
      toJson(
        Map(
          "name" -> toJson("Kiki"),
          "age" -> toJson(25),
          "email" -> JsNull
        )
      )
    )
  )
)

これは次の Json を生成します:

{
  "users":[
    {
      "name": "Bob",
      "age": 31.0,
      "email": "[email protected]"
    },
    {
      "name": "Kiki",
      "age":  25.0,
      "email": null
    }
  ]
}

Next: Json の読み/書き/フォーマットの結合