JSON Toolkit

JSON Pointer, Patch, dan Merge Patch

Inti pati: Tiga spesifikasi pendek membentuk protokol perubahan: Pointer mencari, Patch menyunting dengan tepat, Merge Patch menggabung secara separa. Pilihan yang salah merosakkan tatasusunan dan keserentakan.

Pertengahan~12 min bacaanDiterbitkan 2026-10-30Dikemas kini 2026-10-30
Kulit untuk JSON Pointer, Patch, dan Merge Patch

Intinya: gunakan JSON Pointer untuk mencari mengikut kedudukan dan JSONPath untuk menapis atau membuat rekursi; serahkan sisip/buang tatasusunan, tulisan null, dan penegasan versi kepada JSON Patch, dan simpan Merge Patch untuk konfigurasi rata dan sumber borang.

Mencari dan mengubah adalah dua perkara berbeza, dan spesifikasi memisahkannya kepada tiga dokumen. Harga biasa kerana mengelirukannya ialah "seluruh tatasusunan telah diganti" dan tiada siapa yang menyedarinya.

1. JSON Pointer: Sintaks Pencarian Terpendek

RFC 6901 melakukan satu perkara sahaja: menunjuk kepada nod dalam dokumen dengan rentetan.

""             → the whole document
"/store"       → the store member under the root
"/store/book"  → one level deeper
"/store/book/0/title" → item 0 of the array

Tiga peraturan:

  1. Rentetan kosong menunjuk kepada nod akar itu sendiri, bukan kepada "nod kosong";
  2. Setiap / memisahkan satu token rujukan; tatasusunan menggunakan indeks perpuluhan (sifar di hadapan tidak dibenarkan, jadi 01 tidak sah);
  3. Di dalam token, ~ ditulis sebagai ~0 dan / sebagai ~1 — gantikan ~ sebelum /, jika tidak anda akan berakhir dengan pelepasan berganda.
/**
 * Escapes a single reference token into a JSON Pointer segment.
 * @param token Original key name or index
 * @returns Escaped token
 */
function escapePointerToken(token: string): string {
  return token.replace(/~/g, '~0').replace(/\//g, '~1');
}

/**
 * Joins an array of key names into a JSON Pointer.
 * @param tokens Array of key names or indices
 * @returns Pointer string beginning with /
 */
function buildPointer(tokens: Array<string | number>): string {
  return tokens.map((token) => `/${escapePointerToken(String(token))}`).join('');
}

Nilainya paling jelas apabila kunci itu sendiri mengandungi /: penuding untuk {"a/b": 1} ialah /a~1b, bukan /a/b (yang kedua bermaksud b di bawah a).

Apabila digunakan di dalam URL, pengekodan peratus juga diperlukan, kerana # dan ~ mempunyai makna khas dalam serpihan.

2. JSON Patch: Urutan Atomik Enam Operasi

RFC 6902 menerangkan perubahan dengan tatasusunan operasi, jenis media application/json-patch+json:

[
  { "op": "test", "path": "/version", "value": 7 },
  { "op": "replace", "path": "/name", "value": "新的名字" },
  { "op": "add", "path": "/tags/-", "value": "json" },
  { "op": "remove", "path": "/temp" },
  { "op": "copy", "from": "/name", "path": "/alias" },
  { "op": "move", "from": "/tags/0", "path": "/tags/1" }
]
Operasi Semantik Sempadan
add Menambah anggota atau menyisip elemen tatasusunan Indeks tatasusunan boleh sama dengan panjang (tambah); - bermaksud penghujung
remove Membuang Laluan mesti wujud
replace Mengganti nilai sedia ada Laluan mesti wujud (bersamaan remove + add, tetapi memerlukan laluan wujud dahulu)
move Memindahkan Anda tidak boleh memindahkan nod di bawah sublaluannya sendiri
copy Menyalin Sumber mesti wujud
test Menegaskan kesamaan Jika gagal, keseluruhan patch tidak digunakan; digunakan untuk penguncian optimistik

Sifat utama ialah sifat atomik: jika mana-mana operasi gagal, keseluruhan patch melakukan rollback, jadi tiada keadaan pertengahan di mana "tiga yang pertama digunakan dan yang keempat gagal". Ini menjadikannya badan permintaan PATCH yang hebat.

2.1 Menggunakan test untuk keserentakan optimistik

[
  { "op": "test", "path": "/etag", "value": "a3f9c1" },
  { "op": "replace", "path": "/status", "value": "paid" }
]

Klien mula-mula GET etag dan menulisnya ke dalam operasi test. Pada tulisan serentak, test permintaan kedua gagal dan pelayan memulangkan 409 Conflict atau 412 Precondition Failed. Ini jauh lebih selamat daripada last-write-wins dan tidak memerlukan kunci teragih.

2.2 Operasi tatasusunan paling mudah berlaku kesilapan

// Want to insert at index 1
{ "op": "add", "path": "/items/1", "value": {...} }   // Correct: insert; the original index 1 and everything after shift back

// Want to replace index 1
{ "op": "replace", "path": "/items/1", "value": {...} } // Correct: replace

add pada indeks tatasusunan n ialah sisipan, bukan penimpaan; indeks di luar julat (n > length) mesti menimbulkan ralat. Sesetengah pelaksanaan bersikap longgar tentang ini, yang menjadikan patch yang sama berkelakuan berbeza merentas pelayan.

3. Merge Patch: Serpihan yang Kelihatan Seperti Sasaran

RFC 7386 hanya mempunyai empat peraturan:

  1. Sasaran ialah objek dan patch ialah objek → gabung secara rekursif;
  2. Nilai kunci dalam patch ialah null → padam kunci itu daripada sasaran;
  3. Nilai kunci dalam patch bukan objek → ganti secara langsung;
  4. Patch itu sendiri bukan objek → keseluruhan sasaran diganti.
// Target
{ "a": "b", "c": { "d": "e", "f": "g" } }

// Patch
{ "a": "z", "c": { "f": null } }

// Result
{ "a": "z", "c": { "d": "e" } }

Jenis media ialah application/merge-patch+json. Ia kelihatan seperti "kemas kini separa", yang menjadikannya sangat mudah disalahgunakan:

  • Tatasusunan hanya boleh diganti sepenuhnya. Ingin memadam list[2]? Merge Patch tidak boleh melakukannya;
  • Anda tidak boleh menulis null. null sudah digunakan untuk bermaksud pemadaman;
  • Ia tidak dapat menyatakan keadaan ketiga antara "biarkan medan ini" dan "padam medan ini" (contohnya, menetapkannya secara eksplisit kepada kosong).

4. Matriks Keupayaan: Mana yang Perlu Dipilih

Keperluan JSON Patch Merge Patch
Ganti elemen tatasusunan tertentu dengan tepat ✓ ✗
Padam satu item daripada tatasusunan ✓ ✗
Kemas kini separa beberapa medan ✓ (berbilang replace) ✓ (paling ringkas)
Tulis nilai null ✓ ✗
Kawalan keserentakan (penegasan versi) ✓ (test) ✗ (hanya melalui If-Match)
Keterbacaan patch Sederhana Baik (kelihatan seperti data itu sendiri)
Kerumitan pelaksanaan Tinggi Rendah
Bentuk sumber yang sesuai Bersarang mendalam, seperti senarai Konfigurasi rata, borang

Peraturan praktikal:

  • Borang, halaman tetapan, sumber konfigurasi → Merge Patch; frontend hanya menghantar medan yang berubah;
  • Penambahan dan pembuangan senarai, peralihan mesin keadaan, apa-apa yang memerlukan perlindungan keserentakan → JSON Patch;
  • Kedua-duanya boleh wujud bersama: endpoint berbeza mengisytiharkan Content-Type berbeza, dan pelayan menghantar mengikut jenis.

5. Nota Pelaksanaan

import { applyPatch, compare } from 'fast-json-patch';

const document = { version: 7, items: [{ id: 1 }, { id: 2 }] };
const patch = compare(document, { version: 8, items: [{ id: 1 }] });

// test operations are validated by default; setting mutateDocument to true modifies in place, use with caution
const result = applyPatch(document, patch, true, false);
console.log(result.newDocument);

Empat perkara yang mudah terlepas pandang:

  1. Jangan ubah objek kongsi di tempat; patch yang gagal meninggalkan keadaan separuh terubah (kecuali pelaksanaan menjamin rollback);
  2. Pengesahan laluan: patch dari luar mesti mengesahkan setiap path/from dan menolak kunci seperti __proto__ dan constructor;
  3. Had saiz: hadkan bilangan entri patch untuk menghalang satu patch daripada melaksanakan ratusan ribu operasi;
  4. Pengauditan: patch secara semula jadi sesuai untuk log audit — ia ialah penerangan lengkap tentang "apa yang berubah".

6. Semantik Ralat

Keadaan Kod status yang dicadangkan Penjelasan
Patch bukan tatasusunan / tidak sah dari segi struktur 400 Bad Request Format badan permintaan salah
Nod yang dirujuk oleh path tidak wujud 422 Unprocessable Entity Sah secara sintaksis tetapi tidak boleh dilaksanakan secara semantik
Operasi test gagal 409 atau 412 Menunjukkan konflik versi
Tiada kebenaran untuk mengubah laluan itu 403 Forbidden Jangan menyamarkannya dengan 404 kecuali anda sengaja menyembunyikan kewujudan sumber

Membezakan 400 daripada 422 adalah penting: yang pertama bermaksud "saya tidak dapat memahami patch anda", yang kedua bermaksud "saya faham, tetapi data ini tidak membenarkan perubahan itu".

7. Senarai Semak

  • [ ] Endpoint mengisytiharkan Content-Type yang betul (json-patch+json / merge-patch+json);
  • [ ] Penggunaan patch adalah atomik, dan kegagalan tidak meninggalkan hasil separa;
  • [ ] Patch luaran disahkan terhadap senarai putih laluan dan had bilangan entri;
  • [ ] Kawalan keserentakan menggunakan test atau If-Match;
  • [ ] Patch ditulis ke log audit;
  • [ ] Senario tatasusunan secara eksplisit memilih JSON Patch; jangan harapkan Merge Patch.

FAQ

Bagaimana saya memutuskan antara JSON Patch dan Merge Patch?

Gunakan JSON Patch apabila anda perlu mengganti atau membuang elemen tatasusunan tertentu, menulis nilai null, atau menegaskan versi; gunakan Merge Patch untuk borang dan sumber konfigurasi yang hanya mengemas kini beberapa medan. Merge Patch tidak boleh memadam satu item tatasusunan, dan ia tidak boleh menulis null, kerana null sudah bermaksud pemadaman.

Mengapa add pada indeks tatasusunan n bukan penimpaan?

Kerana add menyisip ke dalam tatasusunan: elemen pada indeks n dan segala selepasnya beralih ke belakang. Untuk mengganti elemen sedia ada anda mesti menggunakan replace, dan laluan mesti wujud dahulu. Indeks di luar julat (n > length) sepatutnya menimbulkan ralat; pelaksanaan yang longgar menjadikan patch yang sama berkelakuan berbeza merentas pelayan.

Patutkah saya mengubah objek asal di tempat?

Tidak. Patch yang gagal meninggalkan keadaan separuh terubah kecuali pelaksanaan menjamin rollback. Gunakan patch pada salinan yang disalin secara mendalam atau structure-clone, kemudian tukar rujukan selepas berjaya.