परफ़ॉर्मेंस और सुरक्षा FAQ: धीमा, अटकता और अविश्वसनीय डेटा
मुख्य बात: कुछ दर्जन MB की JSON फ़ाइल क्यों नहीं खुलती, डीप क्लोन कितना महँगा है, __proto__ प्रदूषण कैसे रोकें, और XSS कहाँ से घुसता है — लक्षण के अनुसार सजाए गए दस प्राथमिक-उपचार निष्कर्ष।

मुख्य बात: पहले लक्षण से निदान करें — पार्सिंग, रेंडरिंग, या मेमोरी — फिर इस क्रम में समाधान करें: डेटा मात्रा घटाएँ, डीप क्लोनिंग रोकें, पार्सिंग को Worker में ले जाएँ और virtual scrolling लगाएँ, और 50 MB से ऊपर सब कुछ स्ट्रीम करें। अविश्वसनीय इनपुट के लिए, __proto__, constructor और prototype फ़िल्टर करने से पहले आकार और नेस्टिंग गहराई सीमित करें।
"JSON धीमा है" दस में से नौ बार गलत होता है; वास्तव में धीमी चीज़ आमतौर पर रेंडरिंग, डीप क्लोनिंग, या ऑपरेशनों का गलत क्रम होती है।
1. यह क्यों धीमा है
बड़ी JSON फ़ाइल की पार्सिंग धीमी है। इसे कैसे ऑप्टिमाइज़ करूँ?
पहले अवरोध का पता लगाएँ, ऑप्टिमाइज़ेशन की बात बाद में करें। चरण कई परिमाणों (orders of magnitude) में भिन्न होते हैं:
| चरण | 10 MB डेटा के लिए मोटा समय | यह अवरोध क्यों है |
|---|---|---|
| नेटवर्क ट्रांसफ़र | बैंडविड्थ पर निर्भर | असंपीड़ित JSON आमतौर पर लगभग 1/5 तक संपीड़ित हो जाता है |
JSON.parse |
20–80 ms | नेटिव कार्यान्वयन, पहले से तेज़ |
| रिकर्शन ट्रैवर्सल / डीप क्लोन | 200–2000 ms | शुद्ध JS में नोड-दर-नोड प्रोसेसिंग |
| DOM रेंडरिंग (tree view) | सेकंड, आसानी से फ़्रीज़ | नोड संख्या विस्फोटक हो जाती है |
इसलिए ऑप्टिमाइज़ेशन का क्रम होना चाहिए:
- डेटा मात्रा घटाएँ: API को पेजिनेट करें, केवल आवश्यक फ़ील्ड लौटाएँ, और gzip/brotli सक्षम करें;
- डीप क्लोनिंग से बचें: अगला भाग देखें; यह सबसे सामान्य छिपी लागत है;
- लेज़ी रेंडरिंग: tree view में केवल विस्तारित स्तर रेंडर करें, एक साथ हज़ारों DOM नोड न बनाएँ;
- स्ट्रीमिंग: डेटा 10 MB से अधिक हो तो सब कुछ एक बार में पार्स न करें।
अगर अवरोध वाकई JSON.parse है, तो इसका मतलब डेटा पहले से कुछ दर्जन MB या अधिक है, और सही कदम स्ट्रीमिंग पार्सर है:
import { parser } from 'stream-json';
import { streamArray } from 'stream-json/streamers/StreamArray';
// array तत्वों को एक-एक करके संभालें; मेमोरी उपयोग कुल आकार से स्वतंत्र रहता है
const pipeline = fs.createReadStream('big.json')
.pipe(parser())
.pipe(streamArray());
pipeline.on('data', ({ value }) => {
processItem(value);
});
कुछ दर्जन MB की JSON खुलते ही ब्राउज़र क्रैश कर देती है। अब क्या?
पहले पुष्टि करें कि यह "क्रैश" है या "फ़्रीज़"। दोनों को पूरी तरह अलग संभालना पड़ता है:
- फ़्रीज़: टैब "Page unresponsive" दिखाता है और कई दर्जन सेकंड बाद ठीक हो सकता है। कारण मुख्य थ्रेड को संतृप्त करने वाले लंबे टास्क हैं;
- क्रैश: टैब "Aw, Snap" बन जाता है, आमतौर पर मेमोरी समाप्ति (OOM)।
किसी भी स्थिति में, इसे मुख्य थ्रेड पर संसाधित न करें। पार्सिंग को Web Worker में ले जाएँ:
// worker.js
self.onmessage = (event) => {
const parsed = JSON.parse(event.data);
self.postMessage(parsed);
};
Worker "UI फ़्रीज़ न हो" हल करता है, मेमोरी सीमा नहीं। ब्राउज़र टैब में आमतौर पर 1–4 GB उपलब्ध होता है; 200 MB की JSON ऑब्जेक्ट में पार्स होकर 1 GB या अधिक ले सकती है (हर key-value जोड़ी पर ऑब्जेक्ट ओवरहेड होता है), और उसके ऊपर रेंडरिंग हो तो सीमा आसानी से पार हो जाती है।
असली समाधान है कभी सब कुछ लोड न करना:
| डेटा आकार | तरीका |
|---|---|
| < 5 MB | सीधे JSON.parse से पार्स करें, रेंडरिंग का ध्यान रखें |
| 5–50 MB | Worker पार्सिंग + virtual scrolling |
| > 50 MB | स्ट्रीमिंग पार्सिंग, केवल आवश्यक फ़ील्ड रखें |
| > 500 MB | फ़ॉर्मैट बदलें (Parquet / SQLite / शार्डेड JSON Lines) |
क्या JSON.parse(JSON.stringify(obj)) डीप क्लोन है?
यह डीप क्लोन है, लेकिन कई जगहों पर "चुपचाप डेटा बदल देता है", इसलिए सामान्य क्लोनिंग टूल के रूप में उपयुक्त नहीं है।
const source = {
date: new Date(),
fn: () => {},
undef: undefined,
nan: NaN,
map: new Map(),
nested: { a: 1 },
};
JSON.parse(JSON.stringify(source));
// {
// date: "2026-10-30T03:00:00.000Z", // Date string बन जाता है
// nan: null, // NaN null बन जाता है
// nested: { a: 1 } // केवल यह स्तर सही है
// }
// fn और undef गायब हो जाते हैं, map {} बन जाता है
समस्याओं की सूची:
| प्रकार | परिणाम |
|---|---|
Date |
ISO string बन जाता है |
NaN / Infinity |
null बन जाता है |
undefined / फ़ंक्शन / Symbol |
ऑब्जेक्ट के अंदर गायब हो जाता है, array के अंदर null बन जाता है |
Map / Set |
{} बन जाता है |
BigInt |
TypeError throw करता है |
| सर्कुलर रेफ़रेंस | TypeError throw करता है |
आधुनिक ब्राउज़रों में structuredClone है, जो Date, Map, Set और सर्कुलर रेफ़रेंस समर्थित करता है लेकिन फ़ंक्शन नहीं:
const copy = structuredClone(source);
अगर केवल शैलो कॉन्फ़िग merge है, तो spread ऑपरेटर सबसे सस्ता विकल्प है: { ...config, timeout: 3000 }।
tree view का विस्तार इतना अटकता क्यों है?
क्योंकि हर नोड एक DOM तत्व है, और नोड संख्या हज़ारों तक पहुँचते ही ब्राउज़र की लेयाउट लागत विस्फोटक हो जाती है।
10 MB की JSON में 50k–100k लीफ़ नोड हो सकते हैं। भले ही आप केवल दिखने वाला क्षेत्र रेंडर करें, DOM एक बार बनाने में सैकड़ों मिलीसेकंड से सेकंड लगते हैं, और हर अगला स्क्रॉल reflow ट्रिगर करता है।
सही तरीका:
- Virtual scrolling: व्यूपोर्ट के अंदर केवल कुछ दर्जन पंक्तियाँ रेंडर करें और स्क्रॉल करते समय DOM नोड पुनः उपयोग करें;
- डिफ़ॉल्ट रूप से संक्षिप्त: केवल पहले दो स्तर विस्तृत करें और गहरे स्तर माँग पर रेंडर करें;
- रेंडर सीमा लागू करें: 10k नोड से ऊपर "डेटा बहुत बड़ा है, ढूँढने के लिए search उपयोग करें" दिखाएँ, ज़बरदस्ती रेंडर न करें।
यही कारण है कि इस साइट का एडिटर बड़ी फ़ाइलों के लिए शालीनता से degrade होता है — इसलिए नहीं कि वह तकनीकी रूप से उन्हें रेंडर नहीं कर सकता, बल्कि इसलिए कि रेंडर करना उन्हें अनुपयोगी बना देगा।
2. हमला क्यों होता है
__proto__ इंजेक्शन कैसे काम करता है?
Prototype प्रदूषण। हमलावर __proto__ कुंजी वाली JSON बनाता है; अगर आपका कोड उसे बिना फ़िल्टर किए किसी ऑब्जेक्ट में merge करता है,
तो यह Object.prototype को ओवरराइट कर देता है और रनटाइम के हर ऑब्जेक्ट को प्रभावित करता है।
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
// खतरनाक कोड: कुंजी नाम जाँचे बिना रिकर्शन merge
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object') {
target[key] = target[key] || {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
merge({}, payload);
({}).isAdmin; // true — हर ऑब्जेक्ट प्रदूषित हो चुका है
मुख्य तथ्य: JSON.parse खुद prototype प्रदूषित नहीं करता; {"__proto__": ...} सामान्य own property के रूप में पार्स होता है।
खतरनाक बात पार्सिंग के बाद merge, असाइनमेंट, Object.assign, lodash.merge, और इसी तरह के ऑपरेशन हैं।
तीन सुरक्षाएँ:
- कुंजी नाम फ़िल्टर करें: merge करते समय
__proto__,constructorऔरprototypeछोड़ दें; Object.create(null)को कंटेनर बनाएँ ताकि प्रदूषित करने के लिए कोई prototype चेन न हो;- अविश्वसनीय key-value जोड़ियाँ रखने के लिए
Mapउपयोग करें।
function safeMerge(target, source) {
for (const key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
const value = source[key];
if (value && typeof value === 'object' && !Array.isArray(value)) {
target[key] = safeMerge(target[key] ?? {}, value);
} else {
target[key] = value;
}
}
return target;
}
यह भी ध्यान दें कि for...in इनहेरिट किए गए properties पर चलता है; Object.keys() अधिक सुरक्षित है।
अविश्वसनीय JSON पार्स करते समय किन बातों का ध्यान रखें?
इस क्रम में करें — कम लागत, अधिक लाभ:
- आकार सीमित करें: सीमा से अधिक हर चीज़ को तुरंत अस्वीकार करें, पहले उसे मेमोरी में पढ़े बिना;
- गहराई सीमित करें: सैकड़ों स्तर गहराई तक नेस्टेड JSON रिकर्शन पार्सर का स्टैक ओवरफ़्लो कर सकता है;
- कुंजी संख्या सीमित करें: hash collision हमलों और मेमोरी amplification से बचाता है;
- खतरनाक कुंजियाँ फ़िल्टर करें:
__proto__,constructor,prototype; - संरचना वैलिडेट करें: Schema से गुज़ारकर टाइप और आवश्यक फ़ील्ड की पुष्टि करें;
Content-Typeपर भरोसा न करें: घोषित प्रकार और वास्तविक सामग्री अलग हो सकते हैं।
गहराई जाँच सस्ती है; पार्सिंग से पहले मोटा regex फ़िल्टर उपयोग कर सकते हैं, या पार्सिंग के बाद ट्रैवर्स कर सकते हैं:
function measureDepth(value, depth = 0) {
if (value === null || typeof value !== 'object') {
return depth;
}
let maxDepth = depth;
for (const key of Object.keys(value)) {
maxDepth = Math.max(maxDepth, measureDepth(value[key], depth + 1));
}
return maxDepth;
}
लेकिन यह कोड खुद रिकर्शन है और दुर्भावनापूर्ण रूप से गहरे डेटा पर स्टैक ओवरफ़्लो कर सकता है। सुरक्षित तरीका है सीमा को पार्सिंग के दौरान लागू करना,
ऐसा पार्सर उपयोग करके जो गहराई विकल्प समर्थित करता हो (कुछ लाइब्रेरी maxDepth पैरामीटर देती हैं)।
JSON के माध्यम से XSS कैसे होता है?
JSON खुद HTML नहीं है और निष्पादित नहीं होता। XSS उस बिंदु पर होता है जहाँ "JSON मान को HTML में जोड़ा जाता है"।
तीन सामान्य रास्ते:
| रास्ता | उदाहरण | सुरक्षा |
|---|---|---|
| सर्वर-साइड टेम्पलेट संयोजन | res.send('<div>' + data.name + '</div>') |
आउटपुट पर HTML-escape |
फ़्रंटएंड innerHTML |
el.innerHTML = data.html |
textContent उपयोग करें या sanitize करें |
| इनलाइन script एम्बेडिंग | <script>var d = ${JSON.stringify(data)}</script> |
<, >, & escape करें |
तीसरा सबसे सूक्ष्म है। JSON.stringify < को escape नहीं करता, इसलिए </script><script>alert(1)</script> वाला डेटा
script टैग को जल्दी बंद कर सकता है:
const data = { name: '</script><script>alert(1)</script>' };
// आउटपुट: <script>var d = {"name":"</script><script>alert(1)</script>"}</script>
समाधान है खतरनाक अक्षरों को Unicode escapes में बदलना:
function safeJsonForHtml(data) {
return JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026')
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
}
क्या नेस्टिंग गहराई सीमित करनी चाहिए?
हाँ। यह सबसे अच्छे लागत-लाभ अनुपात वाली सुरक्षाओं में से एक है।
सीमित न करने के परिणाम: रिकर्शन फ़ंक्शन स्टैक ओवरफ़्लो करते हैं, गहराई बहुत अधिक होने पर कुछ कार्यान्वयनों में JSON.parse क्रैश होता है,
और सीरियलाइजेशन भी उसी तरह स्टैक उड़ा देता है। 1 KB का [[[[...]]]] बिना सुरक्षा वाले पार्सर को गिराने के लिए काफ़ी है।
उचित सीमा व्यवसाय पर निर्भर है:
| डेटा प्रकार | सुझाई गई सीमा |
|---|---|
| सामान्य व्यावसायिक ऑब्जेक्ट | 10–20 स्तर |
| Tree संरचनाएँ (org charts, निर्देशिकाएँ) | 50–100 स्तर |
| सामान्य API गेटवे | 20 स्तर |
JSON hijacking क्या है?
एक ऐतिहासिक भेद्यता: शुरुआती ब्राउज़र cross-origin <script src> को JSON लोड और निष्पादित करने देते थे, और हमलावर
Array कॉन्स्ट्रक्टर या Object.prototype.__defineSetter__ को फिर से परिभाषित करके डेटा चुराते थे।
आधुनिक सुरक्षाएँ काफ़ी पूर्ण हैं:
- ब्राउज़र अब
__defineSetter__से array लिटरल hijack करने की अनुमति नहीं देते; - सही
Content-Type: application/jsonscript निष्पादन रोकता है; X-Content-Type-Options: nosniffMIME sniffing रोकता है।
पुराने सिस्टम में अब भी अवशिष्ट जोखिम है। व्यावहारिक सलाह: शीर्ष स्तर पर ऑब्जेक्ट उपयोग करें, नंगा array नहीं।
{"data": [...]} जैसा wrapper रिस्पॉन्स को मान्य JavaScript कथन बनने से रोकता है ({ को कोड ब्लॉक माना जाता है),
जिससे सिंटैक्स स्तर पर hijacking अवरुद्ध होती है।
JSON लॉग करने के जोखिम क्या हैं?
तीन:
- संवेदनशील जानकारी डिस्क पर लिखी जाना: फ़ोन नंबर, ID नंबर, टोकन, और plaintext पासवर्ड लॉगिंग सिस्टम में पहुँच जाते हैं, जिसकी अनुमतियाँ आमतौर पर ढीली और retention अधिक होती है;
- लॉग इंजेक्शन: newlines और नकली लॉग उपसर्ग रखने वाला JSON मान एक रिकॉर्ड को कई में बाँट देता है और audit trail प्रदूषित करता है;
- आकार विस्फोट: पूरा 10 MB रिस्पॉन्स लॉग में लिखने से लॉगिंग सिस्टम जल्दी भर जाता है।
तरीका:
// लॉग करने से पहले redact और truncate करें
function safeLog(data) {
const cloned = structuredClone(data);
for (const key of ['password', 'token', 'idCard', 'phone']) {
if (key in cloned) cloned[key] = '***';
}
const text = JSON.stringify(cloned);
return text.length > 2000 ? text.slice(0, 2000) + '...(truncated)' : text;
}
संरचित लॉगिंग सिस्टम (जैसे pino) newlines खुद escape करते हैं, लेकिन redaction और truncation फिर भी आपका काम है।
3. त्वरित संदर्भ
| लक्षण | पहले क्या जाँचें | समाधान |
|---|---|---|
| धीमी पार्सिंग | डेटा मात्रा, क्या डीप क्लोनिंग शामिल है | चंकिंग/स्ट्रीमिंग, JSON.parse(JSON.stringify()) हटाएँ |
| पेज फ़्रीज़ होना | मुख्य थ्रेड पर लंबे टास्क, रेंडर किए गए नोड की संख्या | Worker + virtual scrolling |
| टैब क्रैश | मेमोरी उपयोग | सब कुछ लोड न करें; स्ट्रीमिंग पर स्विच करें |
| डीप क्लोन डेटा खो देता है | Date, NaN, Map |
structuredClone उपयोग करें |
| ऑब्जेक्ट में अचानक अतिरिक्त properties | Prototype प्रदूषण | __proto__ फ़िल्टर करें, Map उपयोग करें |
| एक script पॉप अप करता है | HTML में जोड़ी गई स्ट्रिंग | < escape करें, textContent उपयोग करें |
| स्टैक ओवरफ़्लो | नेस्टिंग गहराई | 10–20 स्तर तक सीमित करें |
| लॉग भर जाना | पूरा रिस्पॉन्स लॉग हो रहा है | Truncate + redact |