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

सार: पहले जाँचें कि 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 सेवा) इससे टकराएगी।
उपाय (जितना मैं इनकी सिफ़ारिश करता हूँ, उसी क्रम में):
- पूरी पाइपलाइन में ID के लिए स्ट्रिंग इस्तेमाल करें:
{"id": "9007199254740993"}, सबसे कम झंझट; - सर्वर पर उच्च-परिशुद्धता प्रकारों से पार्स करें: Java का
BigInteger/BigDecimal, Go काjson.Number, Python काdecimal.Decimal; - फ़्रंटएंड पर
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) होने के तीन स्रोत
- फ़ाइल स्वयं GBK में है और उसे UTF-8 के रूप में पढ़ा जा रहा है;
- रिस्पॉन्स हेडर में
charset=gbkघोषित है, पर सामग्री वास्तव में UTF-8 है; - यह किसी 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 भेजा गया" की अस्पष्टता न रहे।