Play! frameworkのJsPath.readNullableの挙動
はじめに
Play! framework の JSON ライブラリって、慣れればまぁ普通に使えるけど、ドキュメントがイマイチだし、やりたいことをどう書けばいいかが分からなくて時間を使うことが多い。
今回書くのは、JSONデータである要素が存在しない場合にそれをどう扱うか、という話題。
「なんだ readNullable 使うだけでしょ?」
と思う人もいるかもしれない。まぁ実際にはそうなんだけど、readNullable の挙動が若干分かりにくかったので、それについて。
やりたいこと
JSONデータで、ある要素が存在する場合と存在しない場合が考えられるとき、その要素が存在する場合は Some 、存在しない場合は None としてパースしたい。
具体的な例で説明すると、Facebook APIから以下のようなJSONが返ってくるとする。内容はあるFacebookグループへの書き込み。
{ "data": [ { "message" : "Hi, thanks for the invite!", "from" : { "id" : "100000", "name" : "Test User" }, "to": { "data": [ { "id": "123456789", "name": "K's Secret Group" } ] }, "comments": { "data": [ { "id": "12345_67890", "from": { "id": "2345678901", "name": "Kazu" }, "message": "元気?", "created_time": "2014-10-21T02:16:10+0000" } ], "paging": { "previous": "https://graph.facebook.com/previous", "next": "https://graph.facebook.com/next" } } } ] }
comments って要素の下にコメントが並び(上のJSONでは1件のみ)、件数が多い場合は paging という要素の中に「前へ」と「次へ」のリンクが含まれる、という構造になっている。
方法
readNullable を使う
ただ、書き込みにはコメントが付かない場合もあり、その時には comments 要素自体が存在しない。とりあえず他の要素を無視して、nextのURLだけを取り出したいとする。そもそもJSONから値を取り出す方法が何通りかあるが、ここでは Readsを使う方法でやることとする。
case class NextCommentsUrl(url: Option[String]) val urlReads: Reads[NextCommentsUrl] = ( (__ \ "comments" \ "paging" \ "next").readNullable[String] )(NextCommentsUrl)
とやるところだけど、これだとうまくいかない。
この辺を見ると、 readNullable は親要素が存在していないとダメらしい。今回は comments 自体が存在しないので、これだとエラーになる。
orElse でつなぐハックで
結局は、以下のようなハックで乗り切ることができたけど。
case class NextCommentsUrl(url: Option[String]) val urlReads: Reads[NextCommentsUrl] = ( (__ \ "comments" \ "paging" \ "next").readNullable[String] orElse (__ \ "comments" \ "paging").readNullable[String] orElse (__ \ "comments").readNullable[String]) )(NextCommentsUrl)
まとめ・感想
JsPath.readNullable は、対象要素が存在しない可能性があるときに使うが、対象要素の親要素も存在しない場合にはうまくいかない。その場合は、前の方でで挙げたような、orElse で親要素もチェックする方法で乗り切れるっぽい。
もっと綺麗な方法はないものか。