JSON Patchをキャッチアップしました

# 前書き

- [JavaScript Object Notation (JSON) Pointer](https://www.rfc-editor.org/rfc/pdfrfc/rfc6901.txt.pdf)
- [JavaScript Object Notation (JSON) Patch](https://www.rfc-editor.org/rfc/pdfrfc/rfc6902.txt.pdf)

についてキャッチアップしました。

今は API デザインしているので、`PATCH`に関する操作であれば、
上記の`JSON PATCH`ともう一つ、[JSON Merge Patch](https://www.rfc-editor.org/rfc/pdfrfc/rfc7386.txt.pdf)があります。

今回は`JSON PATCH`についてフォーカスしてキャッチアップしました。

## 由来

[JSON RFC](https://www.rfc-editor.org/rfc/pdfrfc/rfc4627.txt.pdf)は JSON を定義しました。
また、[PATCH Method for HTTP](https://www.rfc-editor.org/rfc/pdfrfc/rfc5789.txt.pdf)は HTTP の PATCH メソッドを定義しました。

もともと PATCH 以外に、PUT というメソッドがあり、
PUT の場合にリソースに対して全て更新するというふうに使われています。
PATCH は PUT と違って、リソースに対して一部を更新することが可能と分かります。

`RFC5789`では PATCH を定義しましたが、具体的なフォーマットに対してどういうふうに操作するべきかを定義していませんでした。
例えば[XML Patch](https://datatracker.ietf.org/doc/html/rfc5261)とかもあります。

`RFC5789`の補足として、JSON データをどうやって PATCH するのかを`RFC6902`と`RFC7386`が生まれてきました。

## JSON Pointer

Patch は JSON ドキュメントの一部を更新できるので、
じゃあどうやってその一部を特定できるのかについては、`JSON Pointer`という概念が出てきました。

`""`はルートを意味しています。
それ以外に、`/`を使ってオブジェクトのプロパティを区切って検索します。

例えば：

```jsonc
{
  "a": {
    "b": 1,
    "c": [2, 3]
  },
  "d": 4
}
```

```
""         全部のドキュメント
"/a"       { "b": 1, "c": [2, 3] }
"/d"       4
"/a/b"     1
"/a/c"     [2, 3]
"/a/c/1"   3
```

これでリソース内の任意の部分を特定できるようになりました。

### 特殊な文字列の処理

後は、
`/`とか`˜`は特殊な意味を持っているので、エスケープ処理が必要です。

```jsonc
{
  "a/b": 1,
  "c˜d": {
    "e": [
      2,
      {
        "f/g": 3
      }
    ]
  }
}
```

それぞれを取得した場合に、

```
"a˜1b"            1
"c˜0d/e"          [2, { "f/g": 3 }]
"c˜0d/e/1/f˜g"    3
```

というふうにエスケープが必要にあります。

- ˜ => ˜0
- / => ˜1

## JSON Patch

というわけで、ドキュメント内の操作したい部分を特定できたので、
次に実際どういう風に操作できるのかを見てみましょう。

### Document Structure

まず実際のリクエストのデータストラクチャーを見てみましょう。

```jsonc
 PATCH /my/data HTTP/1.1
 Host: example.org
 Content-Length: 326
 Content-Type: application/json-patch+json
 If-Match: "abc123"
 [
   { "op": "test", "path": "/a/b/c", "value": "foo" },
   { "op": "remove", "path": "/a/b/c" },
   { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
   { "op": "replace", "path": "/a/b/c", "value": 42 },
   { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
   { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
 ]
```

上記は RFC 内で提供している例です。

ちょっとここで注目してもらいたいのは、`Content-Type`と`If-Match`の部分です。

- Content-Type: application/json-patch+json
- If-Match: 文字列、ID など

もし Content-Type が間違った場合に`415`を返します。
`If-Match`についてなぜ必要なのかは`RFC5789`の中に詳細を書いてありますが、
一言で言いますと、
同時に複数個の PATCH 操作が発生する可能性がありますので、
[空中衝突](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/ETag#caching_of_unchanged_resources)を防止するために、
`If-Match`をつけるのがお勧めですね。
詳細はもっとキャッチアップして記事を書いてみようと思います。

### add

例で説明します。

```jsonc
// 既存のデータ
{
  "a": 1,
  "b": [2, 3],
  "c": {
    "d": 4
  }
}
```

```jsonc
// patch
[
  { "op": "add", "path": "/b/-", "value": 5 }, // -は最後を意味しています
  { "op": "add", "path": "/c/e", "value": 6 },
  { "op": "add", "path": "/f", "value": 7 }
  { "op": "add", "path": "g", "value": 7 } // これはエラーになる、パスが存在しないため
]
```

```jsonc
// result
{
  "a": 1,
  "b": [2, 3, 5],
  "c": { "d": 4, "e": 6 },
  "f": 7
}
```

### replace

```jsonc
// 既存のデータ
{
  "a": 1,
  "b": [2, 3],
  "c": {
    "d": 4
  }
}
```

```jsonc
// patch
[
  { "op": "replace", "path": "/a", "value": 10 },
  { "op": "replace", "path": "/b/1", "value": 30 },
  { "op": "replace", "path": "/c/d", "value": 40 },
  { "op": "replace", "path": "/c/e", "value": 50 } // 存在しないパスのため、エラーが発生する
]
```

```jsonc
// result
{
  "a": 10,
  "b": [2, 30],
  "c": { "d": 40, "e": 50 }
}
```

### remove

```jsonc
// 既存のデータ
{
  "a": 1,
  "b": [2, 3],
  "c": {
    "d": 4
  }
}
```

```jsonc
[
  { "op": "remove", "path": "/a" },
  { "op": "remove", "path": "/b/1" },
  { "op": "remove", "path": "/b/99" }, // 存在しないパスのため、エラーが発生する
  { "op": "remove", "path": "/c/d" }
  { "op": "remove", "path": "/c/e" } // 存在しないパスのため、エラーが発生する
]
```

```jsonc
{
  "b": [2],
  "c": {}
}
```

### move

```jsonc
// 既存のデータ
{
  "a": 1,
  "b": [2, 3],
  "c": {
    "d": 4
  }
}
```

```jsonc
[
  { "op": "move", "from": "/a", "path": "/e" },
  { "op": "move", "from": "/b/0", "path": "/f" },
  { "op": "move", "from": "/c", "path": "/g" },
  { "op": "move", "from": "/c", "path": "/g" } // /cがもうすでに存在しないためエラーが発生する
]
```

```jsonc
{
  "b": [3],
  "e": 1,
  "f": 2,
  "g": { "d": 4 }
}
```

`copy`については割愛する。

### test

```jsonc
// 既存のデータ
{
  "a": 1,
  "b": [2, 3],
  "c": {
    "d": 4
  }
}
```

```jsonc
[
  {
    "op": "test",
    "path": "/b/0",
    "value": 2
  }
]
```

path に対する値が期待値と同じであるかどうかをテストする。

## 最後に

上記のコードを全部[JSON-Patch](https://github.com/Starcounter-Jack/JSON-Patch)でテストしました。