← 全部文章

JSON Patch vs JSON Merge Patch:RFC 6902 vs 7396

JSON Patch (RFC 6902) 送出明確的操作;JSON Merge Patch (RFC 7396) 疊加部分物件。透過範例比較並選出合適的方案。

當一個 HTTP API 需要更新資源的 一部分 時,有兩個 IETF 標準在競爭這份工作:JSON Merge Patch(RFC 7396)與 JSON Patch(RFC 6902)。它們看起來相似,運作方式卻很不一樣 —— Merge Patch 把一個局部物件疊上去,而 JSON Patch 送一份明確的操作清單。本指南說明各自怎麼運作、並排對比,並協助你挑對的那一個。

問題:局部更新

一個 PUT 請求會把整個資源替換掉,在你只想改一個欄位時既浪費又有風險。HTTP PATCH 方法就是為局部更新而生 —— 但 PATCH 沒有定義 body 格式。這兩個標準就是要填這個坑。

JSON Merge Patch(RFC 7396)

Merge Patch 就是目標文件的一份局部版本。伺服器把它疊上去:patch 裡有的 key 被設定,被設為 null 的 key 被 刪除,沒提到的 key 維持原樣。

// 原始
{ "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" }
]

六種操作:

op效果
add新增一個值(或用 /- 追加到陣列尾)
remove刪除路徑上的值
replace替換路徑上的值
move把一個值從一個路徑搬到另一個
copy把一個值複製到另一個路徑
test斷言一個值(失敗則整個 patch 中止)

test 操作讓安全的原子更新成為可能:若文件不在預期狀態,整個 patch 都會被拒 —— 對樂觀並發控制很有用。

正面對比

JSON Merge PatchJSON Patch
RFC73966902
Content-Typeapplication/merge-patch+jsonapplication/json-patch+json
可讀性高 —— 看起來像資料較低 —— 是操作清單
可把值設為 null不可(null 表示刪除)
修改單個陣列元素不可(要整個陣列替換)可(依索引)
條件 / 原子更新不可可(test 操作)
Move / copy不可
最適合簡單的物件欄位更新精準、關心陣列、原子的編輯

什麼時候用哪一個

  • JSON Merge Patch —— 當客戶端主要更新頂層物件欄位,且你看重「body 讀起來像資源」時。簡單設定與個人檔更新很合適。記住:你不能把欄位設為 null 也不能編輯陣列項。
  • JSON Patch —— 當你需要編輯陣列元素、搬移或複製值、把欄位設為 null,或在前置條件下原子地套用變更時。協作編輯與便於稽核的變更集的標準選擇。

HTTP PATCH 語意:冪等與 header

團隊上線 PATCH endpoint 時常踩的兩個 HTTP 細節:

  • PATCH 不必冪等(不像 PUT)。一個帶 {"op":"add","path":"/items/-","value":...} 的 JSON Patch 是追加 —— 套用兩次就追加兩次。若想安全重試,要嘛先用 test 操作斷言狀態,要嘛把操作設計成「重複套用是無動作」(對固定路徑用 replace 而非對 /items/-add)。
  • 用對 Content-Type。 RFC 6902 = application/json-patch+json;RFC 7396 = application/merge-patch+json。伺服器通常依此分支。
  • 樂觀並發。 把 JSON Patch 的 test 操作(或 If-Match ETag header)搭配起來,並發寫入者就無法默默互相覆寫。

值得認識的函式庫

JSON Patch 這邊,fast-json-patch 是事實上的 npm 套件 —— 可以 apply、generate(對兩份文件做 diff 生成 patch)、observe(追蹤被監看物件的變化)。JSON Merge Patch 的規則小到可以內聯手寫,但 json-merge-patch 把那些 null-刪除邊界條件都處理好了。

在程式碼裡套用 patch

// 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-刪除規則。

套用 patch 之後,可以對比 before/after 確認改動正是你想要的 —— 見 如何對比兩份 JSON 檔案,或把兩份都貼到 JSON Diff

常見問題

JSON Patch 與 JSON Merge Patch 有何差異?

JSON Merge Patch(RFC 7396)是文件的局部副本,其中的 null 用來刪 key。JSON Patch(RFC 6902)是明確的操作陣列(add、remove、replace、move、copy、test),以 JSON Pointer 路徑為目標。Merge Patch 較簡單;JSON Patch 較強大。

為什麼 JSON Merge Patch 不能把欄位設為 null?

因為在 Merge Patch 裡 null刪除 一個 key 的信號。若你需要存真正的 null 值,用 JSON Patch 配 replace 操作。

我該用哪個 Content-Type?

application/merge-patch+json 用於 Merge Patch;application/json-patch+json 用於 JSON Patch,都以 HTTP PATCH 方法傳送。

怎麼編輯陣列中的一個元素?

用 JSON Patch 加上像 /items/2 的路徑,或用 /items/- 追加。Merge Patch 只能把整個陣列替換。

相關工具與閱讀