play-json便利だよね？って話

これは [Play framework 2.x Scala Advent Calendar 2013](http://www.adventar.org/calendars/114) の 14日目です。

先日は@seratchさん(2日ぶり5回目)の[Play ドキュメントを Skinny で書くと - Manipulating Results, Session and Flash scopes, Content negotiation](http://seratch.hatenablog.jp/entry/2013/12/13/091738)でした
てか13日のやつ12日深夜の段階で埋まってなかったんですけど、すかさず埋められるセラさんすげぇ…

### play-json的な何か

playでjsonを扱うことは多々あると思います

ありますよね？

play-jsonに関しては正直[本家ドキュメント](http://www.playframework.com/documentation/2.2.x/ScalaJson)以上の資料無いと思います
なので既にplay-jsonゴリゴリ使っているぜ！という方には物足りないかも…

これからplayを触ろうとしている、社内のお隣チーム向けでしょうかｗ
こんなことできるよ的な

あと、play-jsonを使いたくなければ、
[play-json4s](https://github.com/tototoshi/play-json4s)というプラグインとかもあります。

逆にplay-jsonをplay以外で使いたければ2.2系からは使えるようになっています
(2.1系のときは[play-json-alone](https://github.com/mandubian/play-json-alone)とかありました)

```scala
// こんなかんじで使えます

resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"

libraryDependencies ++=Seq(
  "com.typesafe.play" %% "play-json" % "2.2.1"
)
```

### transformが便利というお話

[ここのお話](http://www.playframework.com/documentation/2.2.x/ScalaJsonTransformers)です
最初の英語頑張る気力がない俺のような人向けの[素敵な翻訳版](http://www.playframework-ja.org/documentation/2.1.5/ScalaJsonTransformers)はこちら
versionが2.1.5と2.2.xと差がありますが、play-jsonに関しては大きな差はないはず…
(jacksonのversionが変わったとかはありますが)

ちなみに上のドキュメントの最後の例とか、個人的には「おお…すげぇ…」以上の感想持ってないんですけどね…

### 普段自分が何にtransformを使っているかというと

* ユニットテストで使うJsonを作成するとき
* スコープが限られた範囲でのJsonの変換
  * 今社内のログフォーマットを[json lines 形式](http://jsonlines.org/)にしているので、それをちょっと加工するときとかに使ってます

jsonサンプルを一つ作って、transformで色々と変換してテストに使ってます

こんなかんじに使っています(Specsとしての動作確認してませんｗ)

```scala
import org.specs2._
import play.api.http.MimeTypes
import play.api.libs.json._
import play.api.mvc._
import play.api.test.Helpers._

class HogeSpec extends Specification {

  def is = s2"""

test1 $e1

    """

  val targetController = new TargetController extends Controller // なんかのController

  val json = Json.parse("""
  {
    "user": {
      "name" : "hoge 太郎",
      "age" : 25,
      "sex" : "male",
      "email" : "hoge@example.com",
      "job" : "programmer"
    }
  }
  """)

  def e1 = {
    val res = targetController.hoge()(FakeRequest(Helpers.POST, "/hoge").withJsonBody(json).withHeaders(Helpers.CONTENT_TYPE -> MimeTypes.JSON))
    (status(res) === OK).updateMessage("レスポンスコードが期待したものではない")
  }
}

```

当然年齢を変えてみたり、性別を変えてみたり、jobをneetにしたり
もちろん間違った値を入れてみたり…とかしたいですよね？

そこでtransformの出番です

```scala
import Reads._

val ageTransform = { age:Int => __.json.update( (__ \ 'user \ 'age).json.put(JsNumber(age))) }

val sexTransform = (__ \ 'user \ 'sex).json.update( of[JsString].map{
    case JsString("male") => JsString("female")
    case JsString("female") => JsString("male")
    })

val jobChange = __.json.update( (__ \ 'user \ 'job).json.put(JsString("neet")))

val illigalSex = __.json.update( (__ \ 'user \ 'sex).json.put(JsBoolean(true)))
```

わりと色々な書き方ができてしまうので…
その辺は時と場合とおこのみで…

```scala
  def hogeResponse(js:JsValue) = {
    targetController.hoge()(FakeRequest(Helpers.POST, "/hoge").withJsonBody(js).withHeaders(Helpers.CONTENT_TYPE -> MimeTypes.JSON))
  }

  def e2 = {
    json.transform(ageTransform(30)).fold(
        invalid = e => anError,
        valid = js => (status(hogeResponse(js)) === OK).toResult
    )
  }

  def e3 = {
    json.transform(sexTransform).fold(
        invalid = e => anError,
        valid = js => (status(hogeResponse(js)) === OK).toResult
    )
  }

  def e4 = {
    json.transform(jobChange).fold(
        invalid = e => anError,
        valid = js => (status(hogeResponse(js)) === OK).toResult
    )
  }

  def e5 = {
    json.transform(illigalSex).fold(
        invalid = e => anError,
        valid = js => (status(hogeResponse(js)) === BAD_REQUEST).toResult
    )
  }
```

jobChangeしてニートってなんだかわかりませんが…こんなかんじに使えます


### 合成する

ともすると、次は念のため30歳でneetのケースも確かめたくなるのが人情でしょう?

大丈夫、andThenで合成できます

```scala

  def e6 = {
    val neet30 = ageTransform(30) andThen jobChange
    json.transform(neet30).fold(
      invalid = e => anError,
      valid = js => (status(hogeResponse(js)) === BAD_REQUEST).toResult
    )
  }

```

30歳neetの時はBAD_REQUESTを返す… そいういう仕様にしたいということがわかります…


#### まとめ

要は、変換ルールを書いて組み合わせてtransformに食わせればOK！ ってことです

とは言うものの、上記ぐらい簡単な例だと、年齢とかを引数に取ってJsValueを返すようなメソッド作ればいいかもしれません。
が、Jsonそのものが複雑になってくると引数が増えてきたり…と結構辛くなる場合が多いと思います

また、上記例ではinvalidを単に捨てていますが、返ってくるJsErrorを見ればだいたい何を間違ったのかもわかります
(これはtransformというよりはJsResult便利って話ですけど)

まあ、社内でもあんまりtransform使っているコード見てない（俺の観測範囲が狭い？）ので、そんなに好かれていないのかなぁ…
でも個人的には好きです(※)

transform今まで使ったことねーわーという人、これを気に触ってみてはいかがでしょう


※ 個人的に好きなモノ、気に入ったものはあまり他人が好きになってくれない事多いからこれももしかして…


### 最近社内で聞かれたことまとめ

#### serializeするときにvalueをnullにしたい

```scala

case class Hoge(a:Int, b:Option[String])

val writes = (
  (__ \ 'a).write[Int] ~
  (__ \ 'b).writeNullable[String]
  )(unlift(Hoge.unapply))

val h = Hoge(1, None)

Json.toJson(h)(writes)

```

とやると

```json
{
  "a" : 1
}
```

となります

これを

```json
{
  "a" : 1,
  "b" : null
}
```

にしたいというお話

解

```scala
val writes = (
    (__ \ 'a).write[Int] ~
    (__ \ 'b).write[Option[String]]
    )(unlift(Hoge.unapply))
```
とか

[nullable](https://github.com/playframework/playframework/blob/2.2.1/framework/src/play-json/src/main/scala/play/api/libs/json/JsConstraints.scala#L187)は実装を見るとNoneの場合 `Json.obj()` と完全に何もない空のJsObjectを返します
一方[OptionWrites](https://github.com/playframework/playframework/blob/2.2.1/framework/src/play-json/src/main/scala/play/api/libs/json/Writes.scala#L179)を見ると、Noneの場合は `JsNull` が返ります

という差でキーができたりできなかったり

他にも[optionWithNull](https://github.com/playframework/playframework/blob/2.2.1/framework/src/play-json/src/main/scala/play/api/libs/json/JsConstraints.scala#L238)とかあるっぽいですね 使ったこと無いけど

#### intかStringで来るvalueをどうにかしたい

```json
{
  "id" : 1234,
  "name" : "hoge"
}

{
  "id" : "a123",
  "name" : "fuga"
}
```

古いシステムと新しいシステム混ざっているとありそうですね

```scala
import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.libs.json.Reads._

case class Hoge(id:String, name:String)

val reads = (
    (__ \ 'id).read(of[String] | of[Int].map(_.toString)) ~
    (__ \ 'name).read[String]
    )( Hoge.apply _)

val json = Json.parse("""
{
  "id" : 1234,
  "name" : "hoge"
}
""")

val json2 = json.transform(__.json.update((__ \ 'id).json.put(JsString("a123")))).get

assert(json.validate(reads) == JsSuccess(Hoge("1234", "hoge")))
assert(json2.validate(reads) == JsSuccess(Hoge("a123", "hoge")))
```
とか

書き方色々とありますが、個人的にはこんなかんじでやってます

#### まとめ

上のやつも含めて答えはだいたい[ドキュメント](http://www.playframework.com/documentation/2.2.x/ScalaJsonCombinators)にあります
そう、結構play-json系便利〜ってこれまでも言いたかったんですけど、だいたいドキュメントにあるんで言いづらかったんです…

それと[Reads.scala](https://github.com/playframework/playframework/blob/2.2.1/framework/src/play-json/src/main/scala/play/api/libs/json/Reads.scala) [Writes.scala](https://github.com/playframework/playframework/blob/2.2.1/framework/src/play-json/src/main/scala/play/api/libs/json/Writes.scala) あたりを
ボケーッっと眺めると既に用意してくれているものとかに気がつけますのでオススメ

自分は jodaTime 読み書き用のものが既に準備されているのにそれで気が付きましたｗ


明日は @hiro0107 さんです