JSON Lines और NDJSON: streaming डेटा
मुख्य बात: प्रति पंक्ति एक JSON मान appendability, streaming और एक ही पंक्ति तक सीमित नुकसान दिलाता है; कीमत है समग्र structure का अभाव, इसलिए validation पंक्ति-दर-पंक्ति ही होनी पड़ती है।

सार: जब आप पूरा batch एक साथ process करें तो JSON array इस्तेमाल करें, और जब streaming appends तथा ख़राब-पंक्ति isolation चाहिए तो JSON Lines; अगर आपको अब भी समग्र structure validate करना है, तो line-delimited format पर न जाएँ।
एक बार डेटा memory से बढ़ जाए, या लगातार आता रहे, तो "एक बड़ी array" की संरचना दुश्मन बन जाती है।
1. Format नियम
JSON Lines (.jsonl) और NDJSON (.ndjson) व्यवहार में समानार्थी हैं, और दोनों में नियम छोटे हैं:
- Encoding UTF-8 है, BOM के बिना;
- हर पंक्ति में एक पूर्ण, valid JSON मान होता है;
- पंक्तियाँ
\nसे अलग होती हैं; अंतिम पंक्ति newline पर खत्म हो या न हो; - किसी पंक्ति के भीतर unescaped newline नहीं आ सकता — string के अंदर newline
\nके रूप में लिखा जाना चाहिए; - खाली पंक्तियाँ आमतौर पर नज़रअंदाज़ की जाती हैं, पर सख्त implementation उन्हें invalid माननी चाहिए।
{"time":"2026-10-30T10:41:45+08:00","level":"info","message":"boot"}
{"time":"2026-10-30T10:41:46+08:00","level":"warn","message":"slow query","costMs":812}
{"time":"2026-10-30T10:41:47+08:00","level":"error","message":"timeout","traceId":"a1b2"}
दोनों के बीच बारीक अंतर: JSON Lines website "प्रति पंक्ति एक मान, और मान कोई भी JSON हो सकता है (scalars सहित)" पर ज़ोर देती है, जबकि NDJSON specification "खाली पंक्तियाँ नहीं और top-level scalars नहीं" जैसे अधिक सख्त implementation नियम जोड़ती है। Engineering में, सख्त सेट अपनाने से आप मुसीबत से बचे रहते हैं।
2. JSON array के साथ तुलना
| आयाम | JSON array | JSON Lines |
|---|---|---|
| एक record जोड़ना | पूरी फ़ाइल rewrite करें (closing ] बदलें) |
बस एक पंक्ति जोड़ें |
| Streaming पढ़ना | streaming parser चाहिए जो bracket depth ट्रैक करे | पंक्ति से बाँटें |
| एक corrupt entry | पूरी फ़ाइल unparsable हो जाती है | केवल वही पंक्ति प्रभावित |
| Whole-file validation | एक ही चरण में सब validate करें | केवल पंक्ति-दर-पंक्ति |
| Compression | gzip के बाद समान आकार | वही, और blocks समानांतर में compress हो सकते हैं |
| Nesting व्यक्त करना | मूल रूप से समर्थित | हर पंक्ति भीतर nesting कर सकती है; पंक्तियाँ एक-दूसरे से असंबंधित |
| सामान्य उपभोक्ता | सभी JSON उपकरण | Log सिस्टम, big-data pipelines, LLM training sets |
निष्कर्ष सीधा है: डेटा "records का batch" है या "एक document", यही तय करता है कि कौन-सा format इस्तेमाल करना है।
3. पढ़ना: पंक्ति-दर-पंक्ति streaming
Node में, सबसे सरल तरीका readline है:
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
/**
* JSON Lines फ़ाइल को पंक्ति-दर-पंक्ति पढ़ता है और हर record के लिए callback बुलाता है।
* @param filePath फ़ाइल का path
* @param onRecord एक record के लिए handler
* @returns सफल और विफल पंक्तियों की संख्या वाली statistics
*/
async function readJsonLines(
filePath: string,
onRecord: (record: unknown, lineNumber: number) => void,
): Promise<{ okCount: number; badCount: number }> {
const lineReader = createInterface({
input: createReadStream(filePath, { encoding: 'utf8' }),
crlfDelay: Infinity,
});
let okCount = 0;
let badCount = 0;
for await (const rawLine of lineReader) {
const line = rawLine.trim();
if (!line) {
continue;
}
try {
onRecord(JSON.parse(line), okCount + badCount + 1);
okCount += 1;
} catch {
badCount += 1;
}
}
return { okCount, badCount };
}
इस कोड की memory खपत केवल एक पंक्ति के आकार पर निर्भर है, कुल फ़ाइल आकार पर नहीं — यही line-delimited format का सबसे बड़ा मूल्य है।
3.1 जब केवल कुछ fields चाहिए, पहले JSON.parse न करें
अगर हर record में 50 fields हैं और आपको केवल 3 चाहिए, तो JSON.parse सभी 50 के लिए objects बनाता है।
Streaming parser (stream-json, clarinet) केवल लक्षित fields निकाल सकता है, कीमत अधिक implementation जटिलता है।
सामान्य नियम: जब एक record कुछ सौ KB से कम हो, तो सीधे JSON.parse बुलाना आमतौर पर बेहतर सौदा है।
3.2 Chinese text और BOMs
Windows पर बनी .jsonl फ़ाइलें अक्सर BOM रखती हैं, और पहली पंक्ति पर JSON.parse गारंटीड रूप से fail होगा। पढ़ने से पहले BOM हटाएँ, या पहला chunk stream करते समय पहले तीन bytes जाँचें।
4. लिखना: appends और atomicity
import { appendFile } from 'node:fs/promises';
/**
* एक JSON Lines record जोड़ता है।
* @param filePath लक्ष्य फ़ाइल
* @param record लिखने योग्य record object
* @returns एक promise जो लिखाई पूरी होने पर resolve होता है
*/
async function appendRecord(filePath: string, record: unknown): Promise<void> {
const line = `${JSON.stringify(record)}\n`;
await appendFile(filePath, line, { encoding: 'utf8' });
}
दो बातें ध्यान देने योग्य:
JSON.stringifyके आउटपुट में newlines नहीं होते (जब तक किसी string का\nदो अक्षरों में escape न हुआ हो), इसलिए एक पंक्ति हमेशा ठीक एक record है;- जब कई processes एक साथ append करते हैं, तो PIPE_BUF (Linux पर आमतौर पर 4096 bytes) से छोटा एक
writeatomic होता है; उससे बड़ा कुछ भी गारंटीड नहीं। उच्च-concurrency परिदृश्यों के लिए "प्रति process एक फ़ाइल, disk पर flush के बाद merge" अपनाएँ, या transactions वाला storage इस्तेमाल करें।
5. ख़राब पंक्तियाँ संभालना
यह वह निर्णय है जो line-delimited format आपको पहले से लेना मजबूर करता है:
| रणनीति | किसके लिए उपयुक्त | लागत |
|---|---|---|
| छोड़ें और गिनें | ऐसा डेटा जो gaps सहन करता है, जैसे logs और analytics events | डेटा चुपचाप खोता है, इसलिए alerting अनिवार्य |
| तुरंत fail करें | ऐसा डेटा जो खोना नहीं चाहिए, जैसे billing और invoices | एक ख़राब पंक्ति पूरा batch रोक देती है |
| Dead-letter फ़ाइल में भेजें | ऐसे मामले जिन्हें बाद में backfill करना है | अतिरिक्त storage और replay process |
जो भी चुनें, पंक्ति संख्या और मूल सामग्री का एक अंश दर्ज करें, वरना बाद में समस्या ढूँढ नहीं पाएँगे। JSON.parse error संदेश में पंक्ति संख्या नहीं होती, इसलिए आपको उसे खुद बनाए रखना होता है।
6. Compression और sharding
- gzip
.jsonlको लगातार उच्च अनुपात में compress करता है (field नाम दोहराए जाते हैं), इसलिए.jsonl.gzसुझाया जाता है; - gzip blocks में compress करता है और मूल रूप से streaming decompression समर्थित करता है, इसलिए पूरी फ़ाइल decompress करने की ज़रूरत नहीं;
- बड़ी फ़ाइलों को लगभग
1GBके slices में बाँटें, नामों में sequence numbers रखें, ताकि parallel processing और resumable transfer आसान हो; .jsonlको "array में merge" करके कभी preprocess न करें — यह उसके सारे लाभ फेंक देता है।
7. अन्य big-data formats के साथ समझौते
| Format | Structure | Schema शामिल | किसके लिए अच्छा |
|---|---|---|---|
| JSON Lines | प्रति पंक्ति स्वतंत्र | नहीं (बाहरी अनुबंध चाहिए) | Logs, imports और exports, training data |
| CSV | Tabular | नहीं | Reports, Excel interchange |
| Parquet | Columnar | हाँ | Analytical queries, बहुत बड़े offline datasets |
| Avro | Row-based | हाँ | Kafka messages, schema evolution |
JSON Lines का लाभ यह है कि कोई भी भाषा और कोई भी उपकरण उसे तुरंत पढ़ और लिख सकता है; कमी schema और columnar compression का अभाव है। एक बार डेटा एक मशीन पर फ़िट न हो और आपको बार-बार column-oriented queries चाहिए, तो Parquet पर विचार करने का समय आ जाता है।
8. जाँच सूची
- [ ] फ़ाइल extension संगत है (
.jsonlया.ndjson) और कभी मिलाया नहीं गया; - [ ] Writer "प्रति पंक्ति एक record" की गारंटी देता है, strings के भीतर newlines पहले से escaped हैं;
- [ ] Reader BOM हटाता है, खाली पंक्तियाँ छोड़ता है, और पंक्ति संख्याएँ बनाए रखता है;
- [ ] ख़राब-पंक्ति रणनीति स्पष्ट रूप से परिभाषित और निगरानी योग्य है (ख़राब पंक्तियाँ / कुल पंक्तियाँ);
- [ ] बड़ी फ़ाइलें आकार के अनुसार sliced हैं और gzip व parallel reads के साथ जोड़ी गई हैं;
- [ ] जहाँ field अनुबंध मौजूद है, वहाँ अतिरिक्त schema दिया गया है और पंक्ति-दर-पंक्ति validate होता है।
सामान्य प्रश्न
JSON array और JSON Lines में कैसे चुनें?
देखें कि डेटा records का batch है या एक document। जब records append होते हैं और stream के रूप में पढ़े जाते हैं तो JSON Lines चुनें: appending का अर्थ है अंत में एक पंक्ति लिखना, और पढ़ने के लिए केवल पंक्ति से बाँटना। जब पूरा structure एक ही चरण में validate करना हो तो JSON array रखें, क्योंकि JSON array सब कुछ एक साथ validate करता है जबकि JSON Lines केवल पंक्ति-दर-पंक्ति validate हो सकता है।
एक corrupt पंक्ति पूरी फ़ाइल क्यों नहीं तोड़ती?
JSON Lines में हर पंक्ति एक स्वतंत्र, पूर्ण JSON मान है, और parsing स्वाभाविक रूप से पंक्ति-दर-पंक्ति होती है, इसलिए corrupt डेटा केवल उसी पंक्ति को प्रभावित करता है। JSON array में एक syntax error पूरी फ़ाइल को unparsable बना देता है, इसीलिए line-delimited डेटा logs और analytics events के लिए उपयुक्त है।
क्या JSON Lines को JSON array में बदलना चाहिए?
कर सकते हैं: पंक्ति-दर-पंक्ति पढ़ें, हर record पर JSON.parse बुलाएँ, और उन्हें array में इकट्ठा करें। ऐसा करने के लिए पूरी सामग्री memory में फ़िट होनी चाहिए और इससे streaming reads व one-line appends छूट जाते हैं। .jsonl को array में preprocess करना format के सारे लाभ फेंक देता है, इसलिए यह केवल एकबारगी delivery के लिए उचित है जहाँ डेटा memory में फ़िट हो।