JSON Pointer, Patch और Merge Patch
मुख्य बात: तीन छोटे विनिर्देश मिलकर एक change protocol बनाते हैं: Pointer locate करता है, Patch ठीक-ठीक edit करता है, Merge Patch आंशिक रूप से merge करता है। गलत चुनाव arrays और concurrency पर चोट पहुँचाता है।

सारांश: स्थिति के आधार पर locate करने के लिए JSON Pointer और filter या recursion के लिए JSONPath इस्तेमाल करें; array insert/remove, null writes और version assertions JSON Patch को सौंपें, और Merge Patch को सपाट configuration और form resources के लिए रखें।
Locate करना और modify करना दो अलग चीज़ें हैं, और विनिर्देशों ने इन्हें तीन दस्तावेज़ों में बाँट दिया है। इन्हें मिलाने की आम कीमत "पूरा array ही बदल गया" है और किसी को पता भी नहीं चलता।
1. JSON Pointer: सबसे छोटा locating सिंटैक्स
RFC 6901 ठीक एक काम करता है: किसी स्ट्रिंग से दस्तावेज़ में एक node की ओर इशारा करना।
"" → पूरा दस्तावेज़
"/store" → root के नीचे store सदस्य
"/store/book" → एक स्तर और गहरा
"/store/book/0/title" → array का item 0
तीन नियम:
- खाली स्ट्रिंग root node पर स्वयं इशारा करती है, "खाली node" पर नहीं;
- हर
/एक reference token अलग करता है; arrays दशमलव indices इस्तेमाल करते हैं (अग्रणी शून्य मान्य नहीं, इसलिए01अमान्य है); - किसी token के अंदर
~को~0और/को~1लिखा जाता है —/से पहले~बदलें, वरना double escaping हो जाती है।
/**
* एक reference token को JSON Pointer segment में escape करता है।
* @param token मूल key नाम या index
* @returns Escaped token
*/
function escapePointerToken(token: string): string {
return token.replace(/~/g, '~0').replace(/\//g, '~1');
}
/**
* key नामों के array को JSON Pointer में जोड़ता है।
* @param tokens key नामों या indices का array
* @returns / से शुरू होने वाला pointer स्ट्रिंग
*/
function buildPointer(tokens: Array<string | number>): string {
return tokens.map((token) => `/${escapePointerToken(String(token))}`).join('');
}
इसका महत्व तब सबसे अच्छा दिखता है जब key में स्वयं / हो: {"a/b": 1} का pointer /a~1b है, /a/b नहीं (बाद वाला a के नीचे b का अर्थ देता है)।
URL के अंदर इस्तेमाल होने पर percent-encoding भी आवश्यक है, क्योंकि fragment में # और ~ का विशेष अर्थ होता है।
2. JSON Patch: छह operations का atomic क्रम
RFC 6902 बदलावों को operations के array से बताता है, media type 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" }
]
| Operation | Semantics | सीमा |
|---|---|---|
add |
सदस्य जोड़ता है या array element डालता है | array index लंबाई के बराबर हो सकता है (append); - का अर्थ अंत |
remove |
हटाता है | पथ मौजूद होना चाहिए |
replace |
मौजूदा मान बदलता है | पथ मौजूद होना चाहिए (remove + add के बराबर, लेकिन पहले पथ का मौजूद होना ज़रूरी) |
move |
स्थानांतरित करता है | किसी node को उसके अपने subpath के नीचे नहीं ले जा सकते |
copy |
प्रतिलिपि बनाता है | स्रोत मौजूद होना चाहिए |
test |
समानता जाँचता है | विफल होने पर पूरा patch लागू नहीं होता; optimistic locking के लिए |
मुख्य गुण atomicity है: कोई भी operation विफल होने पर पूरा patch rollback हो जाता है, इसलिए "पहले तीन लागू हुए और चौथा विफल" वाली मध्यवर्ती स्थिति नहीं बनती।
यही इसे बेहतरीन PATCH request body बनाता है।
2.1 optimistic concurrency के लिए test का उपयोग
[
{ "op": "test", "path": "/etag", "value": "a3f9c1" },
{ "op": "replace", "path": "/status", "value": "paid" }
]
Client पहले etag का GET करता है और उसे test operation में लिखता है। समवर्ती writes में दूसरी request का test विफल हो जाता है और server
409 Conflict या 412 Precondition Failed लौटाता है। यह last-write-wins से कहीं अधिक सुरक्षित है और इसके लिए कोई distributed lock नहीं चाहिए।
2.2 Array operations सबसे अधिक गलती-prone हैं
// index 1 पर डालना चाहते हैं
{ "op": "add", "path": "/items/1", "value": {...} } // सही: insert; मूल index 1 और उसके बाद सब पीछे खिसकते हैं
// index 1 को बदलना चाहते हैं
{ "op": "replace", "path": "/items/1", "value": {...} } // सही: replace
Array index n पर add एक insert है, overwrite नहीं; सीमा से बाहर का index (n > length) error उठाना चाहिए।
कुछ implementations इसमें ढीली हैं, जिससे एक ही patch अलग-अलग servers पर अलग व्यवहार करता है।
3. Merge Patch: लक्ष्य जैसा दिखने वाला fragment
RFC 7386 में केवल चार नियम हैं:
- लक्ष्य एक object है और patch एक object है → पुनरावर्ती रूप से merge करें;
- patch में किसी key का मान
nullहै → लक्ष्य से वह key हटाएँ; - patch में किसी key का मान object नहीं है → सीधे बदलें;
- patch स्वयं object नहीं है → पूरा लक्ष्य बदल दिया जाता है।
// लक्ष्य
{ "a": "b", "c": { "d": "e", "f": "g" } }
// Patch
{ "a": "z", "c": { "f": null } }
// परिणाम
{ "a": "z", "c": { "d": "e" } }
Media type application/merge-patch+json है। यह "partial update" जैसा पढ़ा जाता है, जिससे इसे गलत इस्तेमाल करना विशेष रूप से आसान हो जाता है:
- Arrays केवल पूरी तरह बदले जा सकते हैं।
list[2]हटाना है? Merge Patch नहीं कर सकता; nullनहीं लिखा जा सकता।nullपहले ही deletion के अर्थ में लिया जा चुका है;- यह "इस फ़ील्ड को छोड़ दो" और "इस फ़ील्ड को हटा दो" के बीच की तीसरी स्थिति व्यक्त नहीं कर सकता (जैसे उसे स्पष्ट रूप से खाली सेट करना)।
4. क्षमता मैट्रिक्स: किसे चुनें
| आवश्यकता | JSON Patch | Merge Patch |
|---|---|---|
| किसी विशेष array element को ठीक-ठीक बदलना | ✓ | ✗ |
| Array से एक item हटाना | ✓ | ✗ |
| कुछ फ़ील्ड आंशिक रूप से अपडेट करना | ✓ (कई replace) | ✓ (सबसे संक्षिप्त) |
null मान लिखना |
✓ | ✗ |
| Concurrency नियंत्रण (version assertion) | ✓ (test) | ✗ (केवल If-Match से) |
| Patch की पठनीयता | मध्यम | अच्छी (डेटा जैसा दिखता है) |
| Implementation जटिलता | अधिक | कम |
| उपयुक्त resource आकार | गहराई से nested, list-जैसे | सपाट configuration, forms |
अनुभव के नियम:
- Forms, settings pages, configuration resources → Merge Patch; frontend केवल बदले हुए फ़ील्ड भेजता है;
- List में जोड़ना-हटाना, state machine transitions, concurrency सुरक्षा चाहने वाली हर चीज़ → JSON Patch;
- दोनों साथ रह सकते हैं: अलग endpoints अलग
Content-Typeघोषित करते हैं, और server type के आधार पर dispatch करता है।
5. Implementation टिप्पणियाँ
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 डिफ़ॉल्ट रूप से जाँचे जाते हैं; mutateDocument को true करने पर जगह पर बदलाव होता है, सावधानी से इस्तेमाल करें
const result = applyPatch(document, patch, true, false);
console.log(result.newDocument);
चार आसानी से छूटने वाले बिंदु:
- साझा objects को जगह पर न बदलें; विफल patch आधा-बदला हुआ हालत छोड़ देता है (जब तक implementation rollback की गारंटी न दे);
- Path validation: बाहर से आने वाले patches को हर
path/fromजाँचना चाहिए और__proto__तथाconstructorजैसी keys अस्वीकार करनी चाहिए; - आकार सीमाएँ: patch entries की संख्या सीमित करें ताकि एक patch लाखों operations न कर सके;
- Auditing: patches audit logs के लिए स्वाभाविक रूप से उपयुक्त हैं — वे "क्या बदला" का पूरा विवरण हैं।
6. Error semantics
| स्थिति | सुझाया status code | व्याख्या |
|---|---|---|
| Patch array नहीं है / संरचना अमान्य है | 400 Bad Request |
Request body का प्रारूप गलत है |
path से संदर्भित node मौजूद नहीं है |
422 Unprocessable Entity |
वाक्यात्मक रूप से मान्य, पर semantically निष्पादन योग्य नहीं |
test operation विफल हुआ |
409 या 412 |
Version conflict दर्शाता है |
| उस पथ को बदलने की अनुमति नहीं है | 403 Forbidden |
जब तक resource का अस्तित्व जानबूझकर छिपाना न हो, इसे 404 से न छिपाएँ |
400 और 422 में अंतर करना महत्वपूर्ण है: पहला अर्थ "मैं आपका patch समझ नहीं सका", दूसरा अर्थ "मैं समझ गया, लेकिन यह डेटा वह बदलाव अनुमति नहीं देता"।
7. Checklist
- [ ] Endpoint सही
Content-Typeघोषित करता है (json-patch+json/merge-patch+json); - [ ] Patch application atomic है, और विफलता कोई आंशिक परिणाम नहीं छोड़ती;
- [ ] बाहरी patches path whitelist और entry-count सीमा के विरुद्ध जाँचे जाते हैं;
- [ ] Concurrency नियंत्रण
testयाIf-Matchइस्तेमाल करता है; - [ ] Patches audit log में लिखे जाते हैं;
- [ ] Array वाले परिदृश्य स्पष्ट रूप से JSON Patch चुनते हैं; Merge Patch पर भरोसा न करें।
FAQ
JSON Patch और Merge Patch के बीच निर्णय कैसे लूँ?
जब आपको किसी विशेष array element को बदलना या हटाना हो, null मान लिखना हो, या version पर assertion करना हो तो JSON Patch इस्तेमाल करें; जिन forms और configuration resources में केवल कुछ फ़ील्ड अपडेट होते हैं उनके लिए Merge Patch। Merge Patch array का एक item नहीं हटा सकता, और null नहीं लिख सकता, क्योंकि null का अर्थ पहले ही deletion है।
Array index n पर add overwrite क्यों नहीं है?
क्योंकि add array में insert करता है: index n वाला element और उसके बाद सब पीछे खिसकते हैं। मौजूदा element बदलने के लिए replace इस्तेमाल करना होगा, और पथ पहले मौजूद होना चाहिए। सीमा से बाहर का index (n > length) error उठाना चाहिए; ढीली implementations एक ही patch को अलग-अलग servers पर अलग व्यवहार दिलाती हैं।
क्या मूल object को जगह पर ही बदलना चाहिए?
नहीं। विफल patch आधा-बदला हुआ हालत छोड़ देता है, जब तक implementation rollback की गारंटी न दे। Patch को deep-copied या structured-cloned प्रति पर लागू करें, फिर सफलता के बाद reference बदलें।