play-jsonのReadsでundefinedとnullを区別する

```scala
(JsPath \ "prop").readNullable[A]
```
で生成される`Reads[Option[A]]`で、JSONに`"prop"`がない(undefined)な場合と、`"prop"`というプロパティが存在し値がnullの場合を区別する必要がある場合の解決策です。

経緯は後述します。
# 結論
JsPathを拡張するimplicitクラスを作り、`Reads[Option[Option[A]]]`を返す`readOptional`というメソッドを作る。返されるReadsの振る舞いは以下の通り

- 読み込むJsPathがundefinedの場合 -> `JsSuccess(None)`
- 読み込むJsPathの値がnullの場合 -> `JsSuccess(Some(None))`
- 読み込むJsPathに値が存在する場合 -> `JsSuccess(Some(Some(A)))`
- 読み込むJsPathの末尾より前のプロパティが無い場合 -> `JsError("error.path.missing")`
    - (`JsPath \ "a" \ "b"`の場合に、末尾の"b"より前の"a"すら存在しない場合)

定義:

```scala
implicit class JsPathOps(self: JsPath) {
  def readOptional[A](implicit reads: Reads[A]): Reads[Option[Option[A]]] = {
    Reads[Option[Option[A]]] { json =>
      self.applyTillLast(json).fold(identity, _.fold(
        _ => JsSuccess(None),
        _ match {
          case JsNull => JsSuccess(Some(None))
          case js => reads.reads(js).repath(self).map(v => Some(Some(v)))
        }
      ))
    }
  }
}
```
※　JSON系の処理まとめているパッケージがあれば、そこのパッケージオブジェクトに定義するといいと思います。

使い方: 

```scala
// Readsで生成したい型
case class UpdateTaskInput(
  name: Option[String],
  deadline: Option[Option[LocalDateTime]] // 利用するフィールド。Option[Option[X]]とする。
)

implicit val reads = (
  (JsPath \ "name").readNullable[String] and
  (JsPath \ "deadline").readOptional[LocalDateTime] // ⇦ 使ってる
)(UpdateTaskInput.apply _)
```

## 解説
JsPathの`applyTillLast`メソッドは、`Either[JsError, JsResult[JsValue]]`を返します。引数のJsValueに対してJsPathを適用し以下のように値を返します。

- JsPathの末尾より前のプロパティが無い -> `Left(JsError("error.path.missing")`
- JsPathの末尾より前はある -> Right
    - 末尾はない -> `Right(JsError("error.path.missing")`
    - 末尾も存在する -> `Right(JsSuccess(JsValue))`
        - ここの`JsValue`は`JsNull`かもしれない

`identity`はScala標準の引数をそのまま返す関数です。`arg => arg`です。
`readOptional`をちょっと分かりやすくしたバージョンは以下:

```scala
def readOptionalVerbose[A](implicit reads: Reads[A]): Reads[Option[Option[A]]] = {
  Reads[Option[Option[A]]] { json: JsValue =>
    self.applyTillLast(json).fold( // Either[JsError, JsResult[JsValue]]をfold
      (e: JsError) => e, // 末尾より前が無い場合JsError('error.path.missing`)をそのまま返す(identityと同じ)
      (jsResult: JsResult[JsValue]) => {
        jsResult.fold(
          _ => JsSuccess(None), // 末尾が無い(undefined)場合はJsSuccess(None)
          (jsValue: JsValue) => jsValue match {
            case JsNull => JsSuccess(Some(None)) // 末尾があるがnullの場合はJsSuccess(Some(None))
            case js => reads.reads(js).repath(self).map((v: A) => Some(Some(v)))
            // ↑JsNull以外の値なのでReads[A]を呼び出してSome(Some(A))にする
          }
        )
      }
    )
  }
}

```
# 経緯
こういった感じのタスク(Task)モデルがありました。

```scala
case class Task(name: String, deadline: Option[LocalDateTime])
                              // ↑任意の期限
```
このモデル更新のJSON形式のパターンを以下のようにしたい

- `{ "name": "新タスク名", "deadline": null }` -> 新しいタスク名を設定して期限を無し(`None`)にする
- `{ "name": "新タスク名" }` -> 新しいタスク名を設定して`deadline`はそのまま
- `{ "deadline": "2018-01-01T00:00:00" }` -> 新しい期限を設定して`name`はそのまま

そして問題があるReadsは以下

```scala
// タスクアップデート用Input
case class UpdateTaskInput(name: Option[String], deadline: Option[LocalDateTime])

implicit val reads = (
  (JsPath \ "name").readNullable[String] and
  (JsPath \ "deadline").readNullable[LocalDateTime]
)(UpdateTaskInput.apply _)
```
この場合、JsPathの`readNullable[T]`は、以下のように値を設定する`Reads[Option[T]]`を生成します。

- 対象のプロパティがない(undefined)の場合 -> `None`
- 対象のプロパティがあり、値がnullの場合　-> `None`
- 対象のプロパティにnullではない値が存在する場合 -> `Some(Reads[T]の結果)`

今回はundefinedとnullを区別したいのでこれだと不十分です。

一応、JsPathには`readNullableWithDefault`という、デフォルト値をとり、JSONでundefinedならデフォルト値、JSONでnullならNoneとするメソッドがありますが、以下のようなことはしたく無い...

```scala
(JsPath \ "deadline").readNullableWithDefault[LocalDateTime](null)
// JSONでundefinedならnull、JSONでnullならNone, 値があったらSome(LocalDateTime)
// うーん。却下w
```
ということで、Taskの更新の入力値の`deadline`は`Option[Option[LocalDateTime]]`で表現し、独自でメソッドを定義するしかなくなりました…（不本意）