JSON Toolkit

छह प्रकार, संख्या परिशुद्धता और एन्कोडिंग की गड़बड़ियाँ

मुख्य बात: JSON में केवल छह प्रकार हैं, पर उनमें से हर एक आपके डेटा को चुपचाप बदल सकता है। यह लेख संख्या, स्ट्रिंग और null की गड़बड़ियों को एक ही बार में समेटता है।

शुरुआती~12 मिनट पढ़ें2026-10-30 को प्रकाशित2026-10-30 को अपडेट किया गया
छह प्रकार, संख्या परिशुद्धता और एन्कोडिंग की गड़बड़ियों का कवर

सार: पहले जाँचें कि ID या धनराशि वाले फ़ील्ड 2^53 - 1 से बड़े हैं या उन्हें कई दशमलव अंकों की परिशुद्धता चाहिए, फिर जाँचें कि स्ट्रिंग की लंबाई की सीमा code point गिनती है या UTF-16 unit; सुरक्षित पूर्णांक सीमा से बाहर का कोई भी मान parser पर परिशुद्धता बचाने का भरोसा करने के बजाय स्ट्रिंग के रूप में ले जाएँ।

JSON में प्रकार लोगों की सोच से कम हैं, और गड़बड़ियाँ ज़्यादा: संख्याएँ खिसकती हैं, स्ट्रिंग टूटती हैं, और null अनुपस्थित key से अलग चीज़ है।

1. प्रकारों का चित्र

प्रकार शाब्दिक उदाहरण JSON में बाधा सामान्य मैपिंग
Object {"a": 1} Key स्ट्रिंग होनी चाहिए, मान कुछ भी हो सकता है Object / Map / struct
Array [1, "a", null] तत्व विषम (heterogeneous) हो सकते हैं Array / List / slice
String "text" UTF-8, दोहरे उद्धरण String
Number 1, -1.5, 1e9 अग्रणी शून्य नहीं, + नहीं, NaN नहीं double / Decimal
Boolean true / false लोअरकेस bool
null null एकमात्र null मान null / Option::None

दो चीज़ें ध्यान दें जो मौजूद ही नहीं हैं: न तो कोई date प्रकार है, न कोई integer प्रकार। सभी तारीखें स्ट्रिंग हैं, और सभी पूर्णांक तथा फ़्लोटिंग-पॉइंट मान एक ही "number" प्रकार साझा करते हैं।

2. संख्याएँ: double precision डिफ़ॉल्ट उत्तर है

2.1 सुरक्षित पूर्णांक की सीमा

IEEE 754 double precision जितना बड़ा पूर्णांक ठीक-ठीक दर्शा सकता है, वह 2^53 - 1 = 9007199254740991 है। उसके आगे आसन्न पूर्णांकों के बीच अंतराल दिखने लगते हैं:

JSON.parse('{"id": 9007199254740993}').id
// 9007199254740992

JSON.stringify({ id: 9007199254740993 })
// '{"id":9007199254740992}'

यह JSON की समस्या नहीं है; यह "पाठ → double precision" चरण की समस्या है। बीच में JavaScript वाला कोई भी पाइपलाइन (gateway, BFF, frontend, Node सेवा) इससे टकराएगी।

उपाय (जितना मैं इनकी सिफ़ारिश करता हूँ, उसी क्रम में):

  1. पूरी पाइपलाइन में ID के लिए स्ट्रिंग इस्तेमाल करें: {"id": "9007199254740993"}, सबसे कम झंझट;
  2. सर्वर पर उच्च-परिशुद्धता प्रकारों से पार्स करें: Java का BigInteger / BigDecimal, Go का json.Number, Python का decimal.Decimal;
  3. फ़्रंटएंड पर JSON.parse के reviver या बड़ी संख्याएँ संभालने वाले parser (जैसे json-bigint) से फ़ील्ड को BigInt बनाएँ, पर ध्यान रखें कि BigInt को सीधे JSON.stringify में नहीं भेजा जा सकता।

2.2 दशमलव सटीक मान नहीं होते

0.1 + 0.2            // 0.30000000000000004
JSON.stringify(0.1 + 0.2)
// "0.30000000000000004"

धनराशि को हमेशा सबसे छोटी मुद्रा इकाई (पैसा/सेंट) में पूर्णांक के रूप में या स्ट्रिंग के रूप में दर्शाएँ; कभी फ़्लोटिंग पॉइंट से जोड़-घटाएँ नहीं।

2.3 क्रमबद्धता (serialization) के समय संख्या की गड़बड़ियाँ

JSON.stringify({ a: 1e21 })        // {"a":1e+21}
JSON.stringify({ a: -0 })          // {"a":0}
JSON.stringify({ a: Infinity })    // {"a":null}
JSON.stringify({ a: NaN })         // {"a":null}
JSON.stringify({ a: 10n })         // TypeError: Do not know how to serialize a BigInt

Infinity और NaN चुपचाप null बना दिए जाते हैं, जो "डेटा बस गायब हो गया" वाली श्रेणी की सबसे पकड़ में न आने वाली समस्या है।

2.4 भाषा के अनुसार डिफ़ॉल्ट व्यवहार

भाषा डिफ़ॉल्ट संख्या प्रकार जिन मामलों में स्पष्ट रूप से संभालना होगा
JavaScript double बड़े पूर्णांक, BigInt का serialization
Go float64 (encoding/json) परिशुद्धता बचाने के लिए json.Number या Decoder.UseNumber() इस्तेमाल करें
Java (Jackson) शाब्दिक रूप से अनुमानित int/long/double/BigDecimal DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS अनिवार्य करें
Python int (मनमानी परिशुद्धता) / float दशमलव परिशुद्धता बचाने के लिए parse_float=Decimal
Rust (serde_json) i64/u64/f64 मनमानी परिशुद्धता के लिए arbitrary_precision सक्षम करना होगा

3. स्ट्रिंग: UTF-16 से देखा गया UTF-8 बाइट प्रवाह

3.1 लंबाई और अक्षर गिनती एक नहीं हैं

JavaScript में "🎉".length का मान 2 है, क्योंकि emoji पूरक तल (supplementary planes) में रहते हैं और surrogate pair से दर्शाए जाते हैं। यदि सर्वर लंबाई की सीमा "अक्षर गिनती" से लगाता है और फ़्रंटएंड length से जाँचता है, तो दोनों पक्षों में टकराव तय है।

'🎉'.length                 // 2
Array.from('🎉').length     // 1
[...'👨‍👩‍👧'].length           // 5 (including zero-width joiners)

जब सख्त गिनती चाहिए, तो Intl.Segmenter (grapheme cluster के आधार पर) इस्तेमाल करें या बैकएंड पर Unicode code point गिनें, और API दस्तावेज़ में परिभाषा स्पष्ट लिखें।

3.2 अकेला surrogate

"\ud800" जैसा अकेला high surrogate व्याकरण की दृष्टि से वैध JSON है (\uXXXX कोई भी चार hex अंक स्वीकार करता है), पर यह वैध Unicode पाठ नहीं है। ES2019 से JSON.stringify अकेले surrogate को \ud800 के रूप में escape करता है, जिससे आउटपुट "well-formed" रहता है। भाषाओं के पार ले जाते समय ऐसे मान तब अस्वीकार हो जाते हैं जब दूसरा पक्ष Unicode की सख्त जाँच करता है।

3.3 \u2028 और \u2029

पंक्ति विभाजक और अनुच्छेद विभाजक JSON में वैध हैं, पर उन्हें सीधे JavaScript स्रोत कोड या <script> टैग में डालने से सिंटैक्स त्रुटि होती है। JSON को HTML में इनलाइन करते समय उन्हें हमेशा escape करें या स्ट्रिंग से JSON.parse द्वारा पार्स करने का रास्ता चुनें।

3.4 चीनी पाठ के गड़बड़ (mojibake) होने के तीन स्रोत

  1. फ़ाइल स्वयं GBK में है और उसे UTF-8 के रूप में पढ़ा जा रहा है;
  2. रिस्पॉन्स हेडर में charset=gbk घोषित है, पर सामग्री वास्तव में UTF-8 है;
  3. यह किसी middleware से गुज़रता है जो "स्ट्रिंग में डिकोड करके फिर एन्कोड कर देता है", इसलिए चीनी पाठ दूसरी बार ट्रांसकोड हो जाता है।

जाँच का क्रम: पहले बाइट देखें (xxd), फिर रिस्पॉन्स हेडर, और अंत में middleware।

4. null बनाम "यह key मौजूद नहीं है"

JSON में ये दोनों अलग हैं:

{"nickname": null}
{}
  • पहला अर्थ है "फ़ील्ड मौजूद है, मान खाली है";
  • दूसरा अर्थ है "फ़ील्ड मौजूद नहीं है"।

TypeScript में यह string | null और nickname?: string का अंतर बन जाता है; Java में यह "स्पष्ट null" और "डिफ़ॉल्ट मान" का अंतर बन जाता है। कोई सहमति न होने पर ये विभाजन दिखने लगते हैं:

// Frontend A's approach
if (data.nickname === undefined) { /* treat it as having no nickname */ }
// Frontend B's approach
if (!data.nickname) { /* handle null and the empty string together */ }

सिफ़ारिश: API दस्तावेज़ में लिखें "मान अनुपस्थित हो तो एकरूपता से null इस्तेमाल करें, key कभी न छोड़ें", या इसका उल्टा — पर एक ही चुनें; API अपडेट करते समय PATCH शब्दार्थ अपनाएँ, ताकि "भेजा नहीं गया, या null भेजा गया" की अस्पष्टता न रहे।

5. Array और object

5.1 Array "समरूप (homogeneous)" नहीं होते

[1, "two", {"three": 3}, null]

व्याकरण की दृष्टि से वैध, पर प्रकार की परिभाषा लिखना बहुत कठिन हो जाता है। API में array के तत्वों को समरूप रखने का प्रयास करें; विषम (heterogeneous) मामलों के लिए type discriminator फ़ील्ड वाले object का array इस्तेमाल करें।

5.2 Object की key क्रम-रहित हैं, फिर भी बहुत लोग क्रम पर निर्भर हैं

विनिर्देश कहता है कि object "name/value जोड़ों का क्रम-रहित संग्रह" है। व्यवहार में V8 insertion order बनाए रखता है (पूर्णांक key के अलावा, जिन्हें क्रमबद्ध कर दिया जाता है)। key के क्रम पर तर्क बनाना (उदाहरण के लिए "पहली key" लेना) implementation के विवरण पर निर्भर होना है।

5.3 Sparse array और छेद मौजूद नहीं

JavaScript का [1, , 3] क्रमबद्ध होकर [1,null,3] बनता है — छेद null बन जाता है। JSON में "खाली स्लॉट" की कोई धारणा नहीं है।

6. एक चेकलिस्ट जिस पर आप काम कर सकते हैं

  • [ ] सभी ID, धनराशि और अत्यधिक बड़े पूर्णांक API अनुबंध में स्पष्ट रूप से स्ट्रिंग हैं;
  • [ ] API दस्तावेज़ null और छोड़े गए फ़ील्ड के शब्दार्थ बताता है;
  • [ ] स्ट्रिंग लंबाई की सीमाएँ बताती हैं कि वे code point गिनती हैं या UTF-16 unit;
  • [ ] serialization से पहले Infinity / NaN / undefined हटाएँ, या उन्हें स्पष्ट रूप से null बनाएँ;
  • [ ] HTML में इनलाइन किया गया JSON escape किया गया है;
  • [ ] फ़्रंटएंड और बैकएंड दोनों के पास "बड़ा पूर्णांक + emoji + null" को कवर करने वाला अनुबंध परीक्षण है।

अक्सर पूछे जाने वाले प्रश्न

2^53 - 1 से बड़े पूर्णांकों को कैसे संभालूँ?

सबसे कम झंझट वाला तरीका यह है कि ID जैसे फ़ील्ड पूरी पाइपलाइन में स्ट्रिंग के रूप में भेजे जाएँ, उदाहरण के लिए {"id": "9007199254740993"}। सर्वर पर आप Java के BigInteger / BigDecimal, Go के json.Number या Python के decimal.Decimal से परिशुद्धता बचा सकते हैं। फ़्रंटएंड पर json-bigint जैसा parser फ़ील्ड को BigInt बना सकता है, पर याद रखें कि BigInt को सीधे JSON.stringify में नहीं भेजा जा सकता।

serialization के बाद Infinity और NaN null क्यों बन जाते हैं?

JSON का number प्रकार NaN और Infinity जैसे गैर-मानक शाब्दिक रूप स्वीकार नहीं करता, इसलिए JSON.stringify उन्हें बिना कोई त्रुटि दिए चुपचाप null में बदल देता है। यह "डेटा बस गायब हो गया" वाली श्रेणी की सबसे पकड़ में न आने वाली समस्या है। serialization से पहले इन मानों को स्पष्ट रूप से छानें, या अपने व्यवसाय के नियम के अनुसार उन्हें null बनाएँ और लॉग करें।

null भेजूँ या फ़ील्ड पूरी तरह छोड़ दूँ?

दोनों चलेंगे, पर API दस्तावेज़ को एक चुनकर स्पष्ट बताना होगा, वरना हर फ़्रंटएंड अपनी अलग धारणा बना लेगा। उदाहरणों में {"nickname": null} और {} TypeScript में क्रमशः string | null और nickname?: string से मेल खाते हैं। API अपडेट करते समय PATCH शब्दार्थ अपनाएँ, ताकि "भेजा नहीं गया, या null भेजा गया" की अस्पष्टता न रहे।