playframework2のjsonに関するライブラリつくった

https://github.com/xuwei-k/play-json-extra

昨日の夜、数時間くらいで適当に作った。*1モチベーションとしては

case class Foo(a: Int, b: String, c: List[String])

というようなcase classがあった場合、以下のようにReads*2を書くわけですが

implicit val fooReads: Reads[Foo] = (
  (__ \ "a").read[Int] and 
  (__ \ "b").read[String] and 
  (__ \ "c").read[List[String]]
)(Foo.apply _)

上記のようにそのままFoo.applyに渡す場合、Int, String, List[String] の3つの型を書かないといけないのって面倒だと思いませんか?Fooの型から推論できそうですよね?というか、推論できるJsonライブラリがScalaで存在する( https://github.com/argonaut-io/argonaut/ )ので、playでももちろん出来るはずです。というわけで作りました。

使い方は

import play.jsonext._
import play.api.libs.json._

implicit val fooReads: Reads[Foo] =
  CaseClassReads(Foo.apply _)(
    "a", "b", "c"
  )

という感じです。型は書かずにkeyだけ書けばよいです。マクロは使ってません。

play2のJsonには、case classのフィールド名と、Jsonのkey名が同じで良い場合、それさえ省略して

implicit val fooReads: Reads[Foo] = Json.reads[Foo]

で全部やってくれるものもあります。(マクロ使って、case classのフィールド名をコンパイル時に取得してる)
http://www.playframework.com/documentation/2.2.2/ScalaJsonInception
https://github.com/playframework/playframework/blob/2.2.2/framework/src/play-json/src/main/scala/play/api/libs/json/Json.scala#L151-L169

しかし、Playのマクロの使う場合の欠点として

  • applyやunapplyがオーバーロードされてるなど、特定の条件でうまくいかない場合がある(?) *3
  • うかつにcase classのフィールド名を変えてしまうと、jsonのkeyも変わってしまって、シリアライズやデシリアライズの挙動も変わってしまう
  • case classのフィールド名と、jsonのkey名を別のものにしたい場合、最初に書いたように(__ \ "a").read[Int] and と型まで書かないといけなくなる
  • なんとなくマクロは使いたくない人(いるのか?)

というのがあります。

逆に言うと、それだけ(andの形式で書くときより、型が省略できるだけ)で、それ以外にすごいことしてくれるわけではありませんが、地味に便利だと思います。

sonatype経由で、maven centralにpublishしてあるので、githubのREADMEの通りにlibraryDependenciesを1行加えれば使えるはずです。


上記では、Readsの例で説明しましたが、ほぼ同じような感じで、WritesとFormatsの場合も定義してあります。

あと、(このあたりのベストプラクティスがよくわかってないのですが)play-jsonの依存のスコープはcompileではなくprovidedになっていて、明示的に加えないといけないので注意してください。

*1:あまりちゃんと調べてないが、他に似たようなもの作ってる人いないはずだし、play2のmaster見てもなかった

*2:Jsonからcase classへ変換するための型クラスのインスタンス

*3:最新版で改善されてたりしないのだろうか・・・そのあたりまで情報追っかけてない