जब दो JSON दस्तावेज़ भिन्न हों: positional diff, id-आधारित diff, या पहले सामान्यीकरण
मुख्य बात: पचास रिकॉर्ड पचपन हो जाते हैं और diff पचास बदलाव बताता है। टेक्स्ट-diff के ग़लत सकारात्मक, id से ऐरे मिलान, jq सामान्यीकरण और लागू करने योग्य पैच को कवर करता है।

मुख्य निष्कर्ष: पहले सामान्यीकरण करें, फिर तुलना करें — कुंजी क्रम, इंडेंटेशन, और संख्या की वर्तनी मिटाने के लिए jq -S या स्थिर सीरियलाइज़ेशन का उपयोग करें, जब क्रम अर्थहीन हो तो ऐरे को किसी व्यावसायिक कुंजी से सॉर्ट या इंडेक्स करें, और केवल तभी diff को पैच में बदलें।
JSON अंतर टेक्स्ट अंतर नहीं है। फ़ाइलों की स्ट्रिंग के रूप में तुलना करें और कुंजी का पुनर्क्रम पूरा पेज चमका देता है; ऐरे की सूचकांक से तुलना करें और एक रिकॉर्ड डालने पर बाक़ी पचास बदले हुए चिह्नित हो जाते हैं।
ऑर्डर सेवा को एक पैच भेजा गया, और आपके पास बस दो प्रतिक्रियाएँ हैं: पहले पचास रिकॉर्ड, बाद में पचास, बीच में एक नया रिकॉर्ड डाला गया। diff उपकरण पचास बदलाव बताता है, लाल रंग की दीवार, और आप अब भी नहीं बता सकते कि असल में कौन-सा रिकॉर्ड हिला।
उपकरण ख़राब नहीं है। "तुलना करना" JSON पर तीन अलग चीज़ें मानता है, और अधिकांश लोग डिफ़ॉल्ट रूप से सबसे कम उपयोगी वाला चुन लेते हैं।
ग़लत अलार्म को दोहराना: एक डालना, पचास बदलाव
Node 22 की तीस पंक्तियाँ: पचास ऑर्डर बनाएँ ({id, sku, qty}), फिर एक को अंत में जोड़ें और एक को शुरू में डालें, सूचकांक-दर-सूचकांक मिलान से अंतर गिनें।
| संचालन | रिकॉर्ड | स्थिति से बताए गए बदलाव |
|---|---|---|
| अंत में एक जोड़ें | 50 → 51 | 1 |
| शुरू में एक डालें | 50 → 51 | 51 |
वही "एक अतिरिक्त रिकॉर्ड", पचास गुना शोर। कारण सामान्य है: जब आप सूचकांक से तुलना करते हैं, नया एलिमेंट स्थिति 0 पर आता है और हर मौजूदा रिकॉर्ड एक स्लॉट नीचे खिसक जाता है। स्थिति 0 बनाम स्थिति 0, स्थिति 1 बनाम स्थिति 1 — कोई जोड़ी बराबर नहीं।
इसके बजाय id से कुंजीबद्ध Map बनाएँ और वही डेटा ठीक एक प्रविष्टि बताता है: $[id=ORD-999] added।
वह प्रयोग आपको वह नियम देता है जो किसी भी उपकरण सुझाव से अधिक मायने रखता है: क्या ऐरे क्रमबद्ध अनुक्रम है या अक्रमबद्ध समूह, यह तय करता है कि आपको कौन-सी तुलना उपयोग करनी चाहिए।
टेक्स्ट diff JSON के बारे में तीन तरह से झूठ बोलता है
diff a.json b.json कैरेक्टरों की तुलना करता है। JSON एक डेटा संरचना रखता है। वह बेमेल तीन भरोसेमंद प्रकार के ग़लत सकारात्मक पैदा करता है।
1. पुनर्क्रमित ऑब्जेक्ट कुंजियाँ
RFC 8259 का अनुभाग 4 स्पष्ट है: ऑब्जेक्ट "name/value जोड़ों का अक्रमबद्ध संग्रह" है। ये दो दस्तावेज़ एक ही मान हैं:
{ "id": 42, "name": "Ada", "active": true }
{ "active": true, "name": "Ada", "id": 42 }
एक लाइन diff तीनों पंक्तियों को बदला हुआ कहता है। मैंने दस-कुंजी वाले ऑब्जेक्ट का कुंजी क्रम उलटा किया, दोनों को सुंदर छापा, और पंक्ति-दर-पंक्ति तुलना की: सभी दस पंक्तियाँ चिह्नित, जबकि संरचनात्मक तुलना कहती है कि वे बराबर हैं। शोर अनुपात 10:0।
कुंजी क्रम आख़िर बदलता क्यों है? सीरियलाइज़र बदलना, कुंजियों को सॉर्ट करने वाले फ़ॉर्मेटर को चलाना, ऑब्जेक्ट को शब्दकोश या हैश मैप से फिर से बनाना, भाषा रनटाइम के बीच जाना — इनमें से कोई एक भी समान डेटा को डिस्क पर भिन्न दिखाने के लिए पर्याप्त है।
2. व्हाइटस्पेस और इंडेंटेशन
{"a":1} और {\n "a": 1\n} एक ही मान में पार्स होते हैं। एक टेक्स्ट diff न्यूलाइन्स और स्पेस देखता है और सब कुछ चिह्नित करता है।
3. संख्याओं और एस्केप की अलग वर्तनी
1.0 और 1, 1e2 और 100, "\u00e9" और "é" — पार्सिंग के बाद एक ही मान, सीरियलाइज़ करने के बाद अलग टेक्स्ट। यह थोड़ा हल्का है क्योंकि यह कुछ पंक्तियों को छूता है, लेकिन config समीक्षाओं में यह आपको यह सोचने के लिए पर्याप्त है कि कोई संख्या बदल गई।
उल्टा मामला वह है जो टेक्स्ट नहीं देख सकता और संरचना देख सकती है: 5 बनाम "5"। एक अतिरिक्त कोटेशन जोड़ा, लेकिन प्रकार बहक गया — आम तौर पर यह अपडेट के बजाय बग है। एक संरचनात्मक तुलना बिना झिझक इसे modify बताती है।
तीन तुलना मोड, तीन काम
| मोड | क्या तुलना होती है | शोर कहाँ से | आउटपुट | किसके लिए फ़िट |
|---|---|---|---|---|
| टेक्स्ट diff | कैरेक्टर पंक्तियाँ | कुंजी क्रम, इंडेंटेशन, संख्या वर्तनी | पंक्ति संख्याएँ, लाल/हरा | स्थानीयकरण बंडल, स्थिर कुंजी क्रम वाले fixtures |
| संरचनात्मक diff | पार्स किए गए ट्री, पथ से मिलाए गए | ऐरे सूचकांक से मिलाए गए | add/remove/modify वाले पथ | API प्रतिक्रियाएँ, configs, webhooks |
| सेमांटिक diff | ट्री प्लस व्यावसायिक कुंजियाँ | केवल ख़राब कुंजी चुनाव | move/insert डिटेक्शन वाले पथ | रिकॉर्ड सूचियाँ, ऑर्डर, इन्वेंटरी |
संरचनात्मक diff "कुंजी क्रम और फ़ॉर्मेटिंग" परत हल करता है। सेमांटिक diff एक कदम आगे जाकर "ऐरे एलिमेंट पहचान" हल करता है। अंतर कार्यान्वयन जटिलता में नहीं है; यह इस बात में है कि आप उपकरण को बताने को तैयार हैं या नहीं कि कौन-सा फ़ील्ड किसी रिकॉर्ड का पासपोर्ट है।
ऐरे निर्णायक रेखा हैं
एक प्रश्न पूछें: क्या इस ऐरे का क्रम स्वयं डेटा है?
- क्रम डेटा है: माइग्रेशन चरण, सर्च रैंकिंग, चार्ट बिंदु, अप्रूवल चेन। स्थिति बदलना व्यवहार बदलना है, इसलिए positional तुलना सही है।
- क्रम अर्थहीन है: टैग, अनुमति ID सेट, ऑर्डर लाइन आइटम, इन्वेंटरी सूचियाँ। एक डालना अन्य आइटम के आँके जाने के तरीके को प्रभावित नहीं करना चाहिए।
जब क्रम अर्थहीन हो, तो रिकॉर्ड को किसी कुंजी से इंडेक्स करें, उन्हें स्थिति से ठोंकने के बजाय:
// id से मिलाएँ: एक insert या delete एक प्रविष्टि बताता है, बाक़ी आइटम अपनी जगह रहते हैं
function diffById(before, after, path = '$') {
const out = [];
const beforeMap = new Map(before.map((item) => [item.id, item]));
const afterMap = new Map(after.map((item) => [item.id, item]));
for (const [id, value] of afterMap) {
if (!beforeMap.has(id)) out.push({ path: `${path}[id=${id}]`, type: 'added' });
}
for (const [id, value] of beforeMap) {
if (!afterMap.has(id)) out.push({ path: `${path}[id=${id}]`, type: 'removed' });
else if (JSON.stringify(value) !== JSON.stringify(afterMap.get(id))) {
out.push({ path: `${path}[id=${id}]`, type: 'changed' });
}
}
return out;
}
परिपक्व लाइब्रेरी इसे एक विकल्प बना देती हैं। jsondiffpatch सबसे लंबा सामान्य अनुक्रम खोजने के लिए LCS का उपयोग करता है, लेकिन ऐरे के अंदर ऑब्जेक्ट एलिमेंट्स डिफ़ॉल्ट रूप से स्थिति से मिलाए जाते हैं — आधिकारिक दस्तावेज़ ज़ोर देते हैं कि उन्हें सामग्री से मिलाने के लिए आपको objectHash फ़ंक्शन देना ही होगा। पहली बार उपयोग करने वाले अक्सर मान लेते हैं कि लाइब्रेरी बेवक़ूफ़ है; असल में उन्होंने कभी कुंजी पास ही नहीं की।
कुंजी चुनना भी मायने रखता है। id सुरक्षित विकल्प है। sku केवल तब काम करता है जब SKUs अद्वितीय हों। पूरे ऑब्जेक्ट को हैश करना "एक फ़ील्ड बदला" को "एक हटा प्लस एक जोड़ा" बना देता है, जो स्थिति से तुलना करने से बदतर है।
सबसे तेज़ टर्मिनल तरकीब: jq से सामान्यीकरण
यदि आपको लाइब्रेरी नहीं चाहिए और बस एक नज़र चाहिए, तो पहले सामान्यीकरण करें और बाद में diff। jq -S ऑब्जेक्ट कुंजियों को पुनरावर्ती रूप से सॉर्ट करता है और सुसंगत इंडेंटेशन लागू करता है:
# सामान्यीकरण करें, फिर पंक्ति-दर-पंक्ति तुलना करें; कुंजी क्रम और इंडेंटेशन का शोर ग़ायब
diff -u <(jq -S . before.json) <(jq -S . after.json)
# जब ऐरे वाकई अक्रमबद्ध हो, तुलना से पहले कुंजी से सॉर्ट करें
diff -u <(jq -S '.items |= sort_by(.id)' before.json) \
<(jq -S '.items |= sort_by(.id)' after.json)
मैंने printf '{"b":2,"a":1,"arr":[1,2]}' | jq -S . चलाया और {"a":1,"arr":[1,2],"b":2} मिला — कुंजियाँ सॉर्ट, arr अछूता। यह याद रखें: -S केवल ऑब्जेक्ट कुंजियों को सॉर्ट करता है, ऐरे एलिमेंट्स को कभी नहीं। या तो ऐरे को sort / sort_by से स्पष्ट रूप से सॉर्ट करें, या positional शोर स्वीकार करें।
दो जाल:
jqमौजूदा जगह संपादित नहीं कर सकता; यह मानक आउटपुट में लिखता है। प्रोसेस प्रतिस्थापन (<(...)) का उपयोग करें, या एक अस्थायी फ़ाइल लिखें, सत्यापित करें, फिर बदलें;-rस्ट्रिंग्स से कोटेशन हटा देता है, इसलिए आउटपुट में न्यूलाइन्स या शेल-महत्वपूर्ण कैरेक्टर हो सकते हैं। अविश्वसनीय डेटा को आगे पाइप करने से पहले कोट करें।
इस साइट का /diff क्या करता है, और क्या नहीं
अंतर्निहित JSON तुलना उपकरण वही संरचनात्मक दृष्टिकोण अपनाता है: दोनों पक्ष ट्री में पार्स होते हैं और पथ से पुनरावर्ती रूप से मिलाए जाते हैं, जिससे path + type(add/remove/modify) + before/after बनता है। ऑब्जेक्ट कुंजी क्रम परिणाम को प्रभावित नहीं करता, और सब कुछ ब्राउज़र में चलता है — पेस्ट किए गए पेलोड कभी अपलोड नहीं होते।
हालाँकि, इसके ऐरे सूचकांक से मिलाए जाते हैं। यह जानबूझकर किया गया समझौता है, दोष नहीं:
- अंत में जोड़ना, उसी स्थिति को संशोधित करना, या पूरी तरह बदलना साफ़ आउटपुट देता है;
- शुरू या बीच में एक रिकॉर्ड डालने से उसके बाद का सब कुछ
modifyचिह्नित हो जाता है, और आपको खुद पहचानना होता है कि "असल में केवल एक रिकॉर्ड नया है"।
इसलिए जब आप ऑर्डर सूचियों की तुलना करें, सही कदम है पेस्ट करने से पहले दोनों पक्षों को id से सॉर्ट करना। सॉर्ट किया गया इनपुट डालने की जगह को पिन करता है और शोर को एक प्रविष्टि में सिमटा देता है। कोई इंस्टॉलेशन नहीं चाहिए, और यह अधिकांश रोज़मर्रा के मामले कवर करता है।
यदि आपको "लागू करने योग्य पैच उत्पन्न करना" या "हिले हुए एलिमेंट्स का पता लगाना" चाहिए, तो इन-पेज उपकरण वह नहीं करता। वह अगला अनुभाग है।
diff को पैच में बदलना: दो फ़ॉर्मेट, उन्हें न मिलाएँ
मनुष्य diffs पढ़ते हैं; प्रोग्राम पैच लागू करते हैं। JSON में दो पैच फ़ॉर्मेट हैं जिनकी सेमांटिक्स पूरी तरह अलग है।
| आयाम | JSON Patch (RFC 6902) | JSON Merge Patch (RFC 7396) |
|---|---|---|
| आकार | op, path, value वाले संचालनों का ऐरे |
लक्ष्य दस्तावेज़ जैसा आकार वाला JSON ऑब्जेक्ट |
| ऐरे | हाँ, सूचकांक से, /items/0, जोड़ने के लिए /- |
नहीं, ऐरे पूरी तरह बदले जाते हैं |
| कुंजी हटाना | remove |
केवल null से, जो उस फ़ील्ड को नहीं हटा सकता जिसका मान वैध रूप से null है |
| move व्यक्त करना | move / copy |
नहीं |
| किसके लिए फ़िट | ऐरे वाले संसाधन, ऑडिट लॉग, परमाणु अपडेट | सादे ऑब्जेक्ट के आंशिक अपडेट, config वितरण |
JSON Patch में एक संचालन है जिसे लोग छोड़ देते हैं: test।
[
{ "op": "test", "path": "/version", "value": 3 },
{ "op": "replace", "path": "/status", "value": "shipped" }
]
यदि test विफल होता है, तो पूरा पैच अस्वीकृत हो जाता है। यह प्रोटोकॉल में अंतर्निहित आशावादी समवर्ती है — "मैंने केवल इसलिए लिखा क्योंकि मैंने version 3 देखा" की गारंटी के लिए कोई अतिरिक्त If-Match हेडर चाहिए नहीं। PATCH endpoint के लिए यह किसी भी दस्तावेज़ीकरण परंपरा से अधिक मूल्यवान है।
पथ JSON Pointer (RFC 6901) का उपयोग करते हैं: ~0 ~ को एस्केप करता है, ~1 / को एस्केप करता है। स्लैश वाली कुंजियाँ (उदाहरण के लिए कुंजी के रूप में MIME प्रकार) एस्केप होनी चाहिए, अन्यथा पॉइंटर कहीं और पहुँचता है। विवरण JSON Pointer और Patch में हैं।
एक व्यावहारिक समस्या बनी रहती है: diff से स्वचालित रूप से उत्पन्न पैच आम तौर पर ऐरे को सूचकांक से संबोधित करते हैं। सूचकांक उत्पादन में नाज़ुक हैं — एक समवर्ती insert और /items/3 अब उसी रिकॉर्ड की ओर इशारा नहीं करता। या तो उत्पन्न पैच को ऑब्जेक्ट फ़ील्ड तक सीमित करें, या लागू करने से पहले test से महत्वपूर्ण फ़ील्ड सत्यापित करें।
तुलना को CI में डालें: मान असर्ट करें, टेक्स्ट नहीं
तीन नियम पर्याप्त हैं:
- परीक्षणों में पार्स किए गए मान असर्ट करें,
JSON.stringifyका आउटपुट कभी नहीं। Jest मेंtoEqualउपयोग करें; Python मेंjson.loadsके बादassert a == b। कुंजी क्रम और इंडेंटेशन भाग नहीं लेते। - गतिशील फ़ील्ड व्हाइटलिस्ट करें।
requestId,timestamp,traceIdहर रन में भिन्न होते हैं; बिना बहिष्करण के परीक्षण स्थायी रूप से लाल रहेगा। jsondiffpatch मेंpropertyFilterऔर deepdiff मेंexclude_pathsइसी के लिए हैं। - विफलता पर पथ प्रिंट करें, पूरा दस्तावेज़ नहीं। कोई भी लॉग में 5 MB पेलोड नहीं पढ़ता;
$.data.items[id=ORD-042].qty: 2 → 3समस्या तुरंत ढूँढ देता है।
Config ड्रिफ़्ट उसी तरह काम करता है: staging और production configs दोनों को jq -S करें, ऐरे को व्यावसायिक कुंजी से सॉर्ट करें, और जो बचे वह वास्तविक अंतर है। समस्याओं का यह वर्ग — वही डेटा पर्यावरणों के बीच भिन्न दिखना — आम तौर पर अपरिभाषित कॉन्ट्रैक्ट है, जिसके बारे में API कॉन्ट्रैक्ट डिज़ाइन है।
FAQ
क्या कुंजी क्रम अंतर माना जाता है?
नहीं। RFC 8259 ऑब्जेक्ट को अक्रमबद्ध संग्रह के रूप में परिभाषित करता है, इसलिए {"a":1,"b":2} और {"b":2,"a":1} एक ही मान हैं। कोई भी उपकरण जो पुनर्क्रम को बदलाव बताता है, वह JSON तुलना नहीं, टेक्स्ट तुलना कर रहा है।
एक रिकॉर्ड डालने से हर प्रविष्टि बदली हुई क्यों चिह्नित होती है?
क्योंकि उपकरण ऐरे को सूचकांक-दर-सूचकांक मिलाता है। नया एलिमेंट स्थिति 0 पर बैठता है, हर मौजूदा रिकॉर्ड एक नीचे खिसकता है, और कोई दो स्थितियाँ मेल नहीं खातीं। तुलना करने से पहले id जैसी कुंजी से सॉर्ट या इंडेक्स करें, positional तुलना न करें।
क्या ऑनलाइन तुलना उपकरण मेरा डेटा अपलोड करते हैं?
यह कार्यान्वयन पर निर्भर करता है। सर्वर-रेंडर किए गए उपकरण पेलोड बैकएंड को भेजते हैं, जहाँ वह लॉग हो सकता है; क्लाइंट-साइड उपकरण ब्राउज़र में पार्स करते हैं और कुछ अपलोड नहीं करते। उत्पादन configs या उपयोगकर्ता पेलोड की तुलना करने से पहले, पुष्टि करें कि पेज स्थानीय रूप से गणना करता है, या नेटवर्क डिस्कनेक्ट करके फिर कोशिश करें।
क्या diff परिणाम सीधे पैच के रूप में उपयोग हो सकता है?
सीधे नहीं। diff बताता है कि चीज़ें कहाँ भिन्न हैं; JSON Patch बताता है कि कौन-से संचालन किस क्रम में चलाने हैं। उनके बीच एक रूपांतरण परत बैठती है। उत्पन्न पैच भी ऐरे को सूचकांक से संबोधित करते हैं, जो समवर्तीता के तहत टूट जाता है — लागू करने से पहले test संचालन जोड़ें।
दसियों मेगाबाइट की फ़ाइलों की तुलना कैसे करूँ?
पहले स्ट्रीम करें (jq -c प्रति पंक्ति एक रिकॉर्ड लिखता है), या केवल वह सबट्री निकालें जिसकी आपको चिंता है, जैसे jq '.data.items'। दसियों मेगाबाइट की पूरी पुनरावर्ती तुलना अब भी मेमोरी में फ़िट होती है; सौ मेगाबाइट से ऊपर, उसे शार्ड करें या कुंजी से इंडेक्स करें। ठोस मेमोरी और समय के आँकड़ों के लिए, देखें बड़ी JSON प्रदर्शन।
क्या तुलना से पहले ऐरे को id से सॉर्ट करना चाहिए?
हाँ। अंतर्निहित JSON तुलना उपकरण ऐरे को सूचकांक से मिलाता है, इसलिए शुरू या बीच में एक रिकॉर्ड डालने से उसके बाद का सब कुछ modify चिह्नित हो जाता है; पेस्ट करने से पहले दोनों पक्षों को id से सॉर्ट करने से डालने की जगह पिन हो जाती है और शोर एक प्रविष्टि में सिमट जाता है। अपवाद वह है जब ऐरे का क्रम स्वयं डेटा हो — माइग्रेशन चरण, सर्च रैंकिंग, चार्ट बिंदु — जहाँ positional तुलना सही है और सॉर्ट करना ग़लत।