【Play Framework】ReadsでJSONを扱う

## この記事で説明すること
- StringのJSON文字列のparse
- JSONの値をcase classにバインド
    - JSONの値が...
        - nullableの場合
        - 日付の場合
        - ネストされている場合

## 今回扱うJSON
```
{
	"id" : 1,
	"name" : "田中　太郎",
    "sex" : "男",
	"birthday" : "1999-01-20",
	"friends" : {
		"records" : [
			{
				"id" : 2,
				"name" : "山田　二郎",
				"sex" : "男",
                "birthday" : "1970-05-24"
			},
			{
				"id" : 3,
				"name" : null,
				"sex" : null,
                "birthday" : "2002-11-03"
			}
		]
	}
}
```

`id`, `name`, `sex`, `birthday` からなるユーザ情報があり、
`friends` - `records`の下にもネストされている形。

- `id` : 数値, not null
- `name` : 文字列, nullable
- `sex` : 文字列, nullable
- `birthday` : 文字列, not null
- `friends` : 下にrecordsをもつ, nullable
- `records` : 繰り返し要素, not null

とする。

## 実装 

### バインドさせるcase classを実装
```scala
case class User(id: Int, name: Option[String], sex: String, birthday: LocalDate, friends: Seq[User])
```
JSON側でnullableの所はOptionに。

JSONでnullが返ってきたときにデフォルト値を入れる方法の説明用に、
JSONの方の`sex`はnullableだが、あえてこちらはOptionにしていない。

### Readsを実装
ReadsはJSONをcase classに紐づける時のルールみたいなもの。
JsValue.validateメソッド(詳細は後述)で暗黙的引数として要求される。

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

  object User {
    implicit val reads: Reads[User] = (
      (__ \ "id").read[Int] and
      // JSONの値がnullableの時
      (__ \ "name").readNullable[String] and
      // JSONの値がnullならデフォルト値を使いたい時
      (__ \ "sex").readWithDefault("不明") and
      // JSONの値が日付の時
      (__ \ "birthday").read[String].map(LocalDate.parse(_, DateTimeFormatter.ofPattern("yyyy-MM-dd"))) and
      // JSONがネストされている時
      (__ \ "friends").readWithDefault(Seq.empty[User])((__ \ "records").lazyRead(Reads.seq[User]))
    ) (User.apply _)
  }
```

`(__ \ "id")`でJSONのkeyを指定している。
その後ろの`read[Int]`で型を指定する。

#### パターン別JSONの扱い
- JSONの値がnullableの時
    - `readNullable`を使う。Optionに対応し、指定したkeyがないか、値がnullならばNoneが返る。
- JSONの値がnullならデフォルト値を使いたい時
    - `readWithDefault`を使う。指定したkeyがないか、値がnullならば、引数に指定したデフォルト値が返る。
- JSONの値が日付の時
    - 一度Stringで受けて、`map`でLocalDate等に変換する。
- JSONがネストされている時
    - `lazyRead`で遅延評価することで、自分自身(ここでは`reads: Reads[User]`)を再帰的に使用できる。
    - `Reads.seq[A]`は`Reads[A]`を`Reads[Seq[A]]`に変換する。

ちなみにreadその他の定義は以下のようになっており、暗黙的引数として、型パラメータと同型のReadsが要求されている。

```scala
def read[T](implicit r: Reads[T]): Reads[T]
def readNullable[T](implicit r: Reads[T]): Reads[Option[T]]
def readWithDefault[T](defaultValue: => T)(implicit r: Reads[T]): Reads[T]
```
Javaで言うプリミティブ(Intとか)とStringのReadsはPlayFramework側で用意されている。

### JSONのparseとcase classへのバインド

```scala
Json.parse(json).validate[User].get
```
`Json.parse(json)`(jsonはStringのJSON)で、JSON文字列をparseし、`JsValue`型に変換される。
parseに失敗(JSONにカンマが足りなかったりカッコを忘れていたり)すると、`JsonParseException`や`JsonEOFException`等が発生する。

次に`.validate[User]`で、「Readsを実装」の項で実装した`Reads[User]`を使用したバリデーションを実行し、`JsResult`型に変換される。
`JsResult`の実態は、バリデーションOKならば`JsSuccess`、NGならば`JsError`型となる。
`JsSuccess`には、`validate[A]`の型パラメータに指定したクラスのインスタンス(ここではUser)が格納されている。
`JsError`にはバリデーションNGとなった理由が格納されている。

最後の`get`で、インスタンスを取得できる。もし`JsError`だった場合は例外が発生するので注意。
例外を出したくない場合は`get`の代わりに`asEither`や`asOpt`等が用意されているのでお好みで。

以上


## ソース全体

```scala
object PlayJsonExample extends App {

  import java.time.LocalDate
  import java.time.format.DateTimeFormatter

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

  case class User(id: Int, name: Option[String], sex: String, birthday: LocalDate, friends: Seq[User])

  object User {
    implicit val reads: Reads[User] = (
      (__ \ "id").read[Int] and
      (__ \ "name").readNullable[String] and
      (__ \ "sex").readWithDefault("不明") and
      (__ \ "birthday").read[String].map(LocalDate.parse(_, DateTimeFormatter.ofPattern("yyyy-MM-dd"))) and
      (__ \ "friends").readWithDefault(Seq.empty[User])((__ \ "records").lazyRead(Reads.seq[User]))
    ) (User.apply _)
  }

  val json =
    """
      |{
      |	"id" : 1,
      |	"name" : "田中　太郎",
      | "sex" : "男",
      |	"birthday" : "1999-01-20",
      |	"friends" : {
      |		"records" : [
      |			{
      |				"id" : 2,
      |				"name" : "山田　二郎",
      |				"sex" : "男",
      |             "birthday" : "1970-05-24"
      |			},
      |			{
      |				"id" : 3,
      |				"name" : null,
      |				"sex" : null,
      |             "birthday" : "2002-11-03"
      |			}
      |		]
      |	}
      |}
      |""".stripMargin

  val user = Json.parse(json).validate[User].get

  println(user)
  // → User(1, Some(田中　太郎), 不明, 1999-01-20, List(
  //      User(2, Some(山田　二郎), 男,  1970-05-24, List()),
  //      User(3, None,            不明,2002-11-03, List())
  //   ))
}
```