play-jsonについて学ぶ(1)
目的
現在のプロジェクトではJSONライブラリとしてplay-jsonを(play scalaと共に)使用している。 play-jsonはドキュメントは豊富なのでググれば大抵の問題は解決できて便利なのだけれども、ググってもやり方が見つからない場合は解決に時間がかかってしまっていた。 これはplay-jsonの基本を理解していなかったからだと思っていて、そこでplay-jsonのソースコードを読み込んでみた。
この記事の目的はそのとき理解したことの整理だ。
play-jsonの基本
JSONの読み込み
JSONを読み込み、任意のクラスAにマッピングする処理は次の二段階で行われる。
- JSONを
JsValue
へ変換する JsValue
をJsResult[A]
に変換する(ここでA
は任意のクラス)
JsValue
はJSONの抽象構文木で、JSONの値型に対応するサブクラス(JsNull
, JsObject
, JsArray
, JsBoolean
, JsString
, JsNumber
)が存在する。
JsResult[A]
はA
への変換結果を表すクラスで、成功すればJsSuccess[A]
、失敗すればJsError
となる、いわばEither[L, R]
に似たクラスだ。
1の変換はJson.parse
によって行われ、2の変換はReads[A]
によって行われる。
ここでJSONの文法に合致しない不正なJSONが与えられた場合、Json.parse
で使われているJacksonの例外(JsonParseException
など)がスローされる。
2でJsValue
からA
への変換に失敗した場合は例外ではなくJsError
で通知される。JsError
はその失敗原因をerrors: Seq[JsonValidationError]
によって保持している。
Json.parse
はドメインに依存しない処理なので、ユーザはReads[A]
を定義することによって目的の処理を得る。
JSONの書き出し
JSONの書き出しも読み込みと同じくJsValue
を介した二段階で行われる。
- 任意のクラス
A
をJsValue
に変換する JsValue
をJSONに変換する
1の変換はWrites[A]
もしくはOWrites[A]
によって行われ、2の変換はJson.stringify
などで行われる(整形の有無などいくつかのメソッドがある)。
Writes[A]
とOWrites[A]
の違いは、Writes[A]
はA
をJsValue
に変換するがOWrites[A]
はA
をJsObject
に変換するというところだ。
OWrites[A]
はWrites[A]
を継承(trait OWrites[-A] extends Writes[A]
)している。
ほとんどの場合JSONはトップレベルはObjectとなるので、OWrites[A]
の使用が妥当であるように思えるが、後述するような小さなWrites[A]
を組み合わせて大きなWrites[A]
を作っていくスタイルであればWrites[A]
を使用した方が良いだろう。
読み込みの場合と同じく、Json.stringify
はドメインに依存しない処理となるので、ユーザはWrites[A]
を定義することによって目的の処理を得る。
Play FrameworkのActionにおけるplay-json
Reads/Writesのイメージを固めるために、Play Frameworkでのplay-jsonの典型的な使用例を挙げる。
implicit val sampleJsonReads: Reads[SampleJson] = Json.reads[SampleJson] // Reads[SampleJson]の定義。定義の意味については後述する。 def sample: Action[JsValue] = Action.async(parse.json) { implicit request => request.body.validate[SampleJson].fold( errs => Future.successful(BadRequest), // JsErrorの場合の処理(errsはSeq[JsonValidationError]型) json => { // JsSuccessの場合の処理(jsonはSampleJson型) // 何か処理 } ) }
Action[A]
はRequest[A]
を受け取ってResult
を返すRequest[A] => Result
の関数だ。
Request[A]
はA
型に変換されたbodyを持つリクエストという意味なので、このサンプルでのrequest.body
はJsValue
となる。
JsValue#validate[A]
は暗黙的(implicit)にReads[A]
を引数にとりJsResult[A]
を返す。
大抵の場合はJsResult[A]
のfoldを使って異常系と正常系の処理を記述する。
まとめ
ここまでが、play-jsonのReads/Writesを理解する上での基本だと考えている。 自分はこれを理解するまで、Readsを書くのにとても苦労していた。
ということで、次回はReadsの書き方についてまとめてみようと思う。