HTTP API がリソースの 一部 を更新する必要があるとき、二つの IETF 標準が競合します:JSON Merge Patch(RFC 7396)と JSON Patch(RFC 6902)。似て見えますが動作はかなり違います —— Merge Patch は部分オブジェクトを上書き、JSON Patch は明示的な操作の配列を送信。本ガイドは各々の動作、対比、選び方を示します。
問題:部分更新
PUT リクエストはリソース全体を置換するため、フィールドを 1 つだけ変えたいときは無駄でリスキー。HTTP PATCH メソッドは部分更新のために存在しますが、ボディフォーマットは定義しません。これら 2 つの標準がそれを提供します。
JSON Merge Patch(RFC 7396)
Merge Patch は対象ドキュメントの部分版です。サーバが上書きします:パッチ内のキーは設定、null に設定されたキーは 削除、言及されないキーはそのまま。
// 元
{ "title": "Hello", "author": "Ada", "tags": ["a", "b"], "draft": true }
// Merge Patch body (Content-Type: application/merge-patch+json)
{ "title": "Hello World", "draft": null }
// 結果
{ "title": "Hello World", "author": "Ada", "tags": ["a", "b"] }
// ^ title 更新 ^ author そのまま ^ draft 削除 (null だった) 直感的でコンパクト。落とし穴:null が「削除」を意味するため、Merge Patch では値を null に設定できず、配列はまるごと置換しかできません —— 個別配列要素の変更は不可。
JSON Patch(RFC 6902)
JSON Patch は操作の順序付き配列で、各操作は JSON Pointer パスで位置を指定。明示的かつ正確:
// JSON Patch body (Content-Type: application/json-patch+json)
[
{ "op": "replace", "path": "/title", "value": "Hello World" },
{ "op": "remove", "path": "/draft" },
{ "op": "add", "path": "/tags/-", "value": "c" },
{ "op": "test", "path": "/author", "value": "Ada" }
]6 つの操作:
| op | 効果 |
|---|---|
add | 値を追加(または /- で配列末尾に追加) |
remove | パスの値を削除 |
replace | パスの値を置換 |
move | 値をあるパスから別のパスへ移動 |
copy | 値を別のパスへコピー |
test | 値をアサート(失敗ならパッチ全体を中止) |
test 操作は安全なアトミック更新を可能にします:ドキュメ ントが期待状態でなければパッチ全体が拒否される —— 楽観的並行制御に有用。
直接対決
| JSON Merge Patch | JSON Patch | |
|---|---|---|
| RFC | 7396 | 6902 |
| Content-Type | application/merge-patch+json | application/json-patch+json |
| 可読性 | 高 —— データのように見える | 低め —— 操作のリスト |
| 値を null に設定可 | 不可(null は削除) | 可 |
| 単一配列要素の変更 | 不可(配列全体を置換) | 可(インデックスで) |
| 条件付き / アトミック更新 | 不可 | 可(test 操作) |
| Move / copy | 不可 | 可 |
| 最適 | シンプルなオブジェクトフィールド更新 | 精密で配列対応、アトミックな編集 |
どれをいつ使うか
- JSON Merge Patch —— クライアントが主にトップレベルのオブジェクトフィールドを更新し、リソースのように読めるボディを評価するとき。シンプルな設定やプロファイル更新に最適。ただし、フィールドを
nullに設定したり配列項目を編集することはできない点を忘れずに。 - JSON Patch —— 配列要素を編集する、値を移動・コピーする、フィールドを
nullに設定する、または前提条件付きでアトミックに変更を適用する必要があるとき。協調編集や監査に優しい変更セットの標準選択。
HTTP PATCH のセマンティクス:冪等性とヘッダ
PATCH エンドポイントを出荷するチームがよく引っかかる 2 つの HTTP 詳細:
- PATCH は冪等である必要はない(PUT と違い)。
{"op":"add","path":"/items/-","value":...}を含む JSON Patch は追加 —— 2 回適用すれば 2 回追加。安全なリトライが欲しいなら、test操作で先に状態をアサートするか、再適用が no-op になるよう設計(/items/-へのaddではなく固定パスへのreplaceを使う)。 - 正しい Content-Type を使う。 RFC 6902 =
application/json-patch+json、RFC 7396 =application/merge-patch+json。サーバは通常これで分岐します。 - 楽観的並行制御。 JSON Patch の
test操作(あるいはIf-MatchETag ヘッダ)を組み合わせて、並行する書き手が静かに互いを上書きできないようにしましょう。
知っておくべきライブラリ
JSON Patch は fast-json-patch が事実上の npm パッケージ —— 適用、生成(2 つの文書を diff してパッチを生成)、観測(監視対象オブジェクトの変更を追跡)が可能。JSON Merge Patch のルールはインラインで書けるほど小さいですが、json-merge-patch は null 削除のエッジケースを処理して実装します。
コードでパッチを適用する
// JavaScript —— fast-json-patch が RFC 6902 を実装
import { applyPatch } from 'fast-json-patch';
const updated = applyPatch(document, patchOps).newDocument;
// JSON Merge Patch は再帰で簡単に適用できるが、ライブラリ
// (例:json-merge-patch)が null 削除ルールを正しく処理する。パッチ適用後、before と after を diff することで、変更が意図通りであることを確認できます —— 2 つの JSON ファイルを比較する方法 を参照、または両方を JSON Diff に貼り付け。
よくある質問
JSON Patch と JSON Merge Patch の違いは?
JSON Merge Patch(RFC 7396)はドキュメントの部分コピーで、null がキーを削除。JSON Patch(RFC 6902)は明示的な操作配列(add、remove、replace、move、copy、test)で、JSON Pointer パスを対象。Merge Patch はよりシンプル、JSON Patch はより強力。
なぜ JSON Merge Patch はフィールドを null に設定できないの?
Merge Patch では null はキーを 削除 するシグナルだから。実際の null 値を保存する必要があるなら、JSON Patch の replace 操作を使いましょう。
どの Content-Type を使うべき?
application/merge-patch+json は Merge Patch、application/json-patch+json は JSON Patch、どちらも HTTP PATCH メソッドで送信。
配列の 1 要素をどう編集する?
/items/2 のようなパスで JSON Patch を使うか、/items/- で追加。Merge Patch は配列全体を置換することしかできません。
関連ツール & 読み物
- JSON Diff —— パッチが意図通りの変更を生んだか検証
- 2 つの JSON ファイルを比較する方法 —— セマンティック diff の背後にあるアルゴリズム
- RFC 7396: JSON Merge Patch
- RFC 6901 & 6902: JSON Pointer と Patch