JSONL के साथ व्यावहारिक कार्य: खोलना, रूपांतरण और खराब लाइनों की पहचान
मुख्य बात: पूरी .jsonl फ़ाइल को JSON.parse को देकर त्रुटि पाना लगभग कभी यह नहीं दर्शाता कि फ़ाइल खराब है — यह दर्शाता है कि पढ़ने की रणनीति गलत है। यह मार्गदर्शिका JSONL की जाँच और लाइन-दर-लाइन पढ़ने के मानक तरीके, JSON ऐरे के साथ दोनों दिशाओं में रूपांतरण की कमांड, छह प्रकार की खराब लाइनों की तालिका और स्ट्रीमिंग HTTP प्रतिक्रियाओं से NDJSON उपभोग का सही तरीका बताती है।

मूल बात: JSONL फ़ाइल की हर लाइन एक स्वतंत्र और वैध JSON मान है, लेकिन पूरी फ़ाइल एक अकेला JSON दस्तावेज़ नहीं है। अगर पूरी फ़ाइल को JSON.parse या json.load को देना विफल हो जाए, तो दस में से नौ बार फ़ाइल ठीक होती है — आपकी पढ़ने की रणनीति गलत होती है।
हर लाइन में एक रिकॉर्ड, लाइनों के बीच कोई कॉमा नहीं, कोई बाहरी ऐरे नहीं — JSONL "पूरे दस्तावेज़ की संरचना" को छोड़कर जोड़कर लिखने, स्ट्रीमिंग पढ़ने और हर लाइन की स्तर पर दोष-पृथक्करण हासिल करता है। ये तीनों लाभ तभी साकार होते हैं जब फ़ाइल को लाइन-दर-लाइन प्रोसेस किया जाए, और जिस क्षण पूरी फ़ाइल को एक साथ पार्स करने की कोशिश की जाए, तीनों त्रुटियों में बदल जाते हैं।
1. तीन नाम, एक प्रारूप: JSONL, NDJSON, JSON Lines
शुरुआती लोग अक्सर नामों का अंतर सिंटैक्स का अंतर समझ लेते हैं। नहीं है:
| नाम | आम संदर्भ | एक्सटेंशन / media type |
|---|---|---|
| JSON Lines | डेटा इंजीनियरिंग, ML डेटासेट (pandas, Spark, Hugging Face) | .jsonl |
| NDJSON | HTTP स्ट्रीमिंग API, वास्तविक समय का डेटा | .ndjson, application/x-ndjson |
| JSONL | दोनों में से किसी का रोज़मर्रा संक्षिप्त रूप | — |
जो टूल एक को पढ़ता है वह दूसरे को भी पढ़ता है। नाम चुनने का एकमात्र मापदंड आपके डाउनस्ट्रीम इकोसिस्टम की परिपाटी है: Hugging Face डेटासेट तैयार करते समय .jsonl लिखें, स्ट्रीमिंग API देते समय application/x-ndjson सेट करें। एक पोर्टेबल JSONL फ़ाइल तीन नियम पूरे करती है: UTF-8 एनकोडिंग (BOM के बिना), हर लाइन में एक पूर्ण JSON मान, और \n से अलग होती लाइनें। फ़ाइल के अंत में एक न्यूलाइन होना अत्यंत अनुशंसित है, और खाली लाइनें वैध रिकॉर्ड नहीं हैं। प्रोटोकॉल के विवरण और डिज़ाइन का तर्क हमारे JSON Lines और NDJSON अवलोकन में शामिल हैं; यह लेख केवल इस बारे में है कि फ़ाइल हाथ में आने के बाद क्या करना है।
2. निरीक्षण: पहले head, फिर grep, एडिटर सबसे अंत में
500 MB की .jsonl को डबल क्लिक करके एडिटर में खोलना सबसे आम गलती है: तीस लाख लाइनों का टेक्स्ट जमा सिंटैक्स हाइलाइटिंग और अनडू बफ़र लगभग हर एडिटर को फ्रीज़ कर देगा — और इसका दोषी JSON नहीं है। 45 MB दस्तावेज़ को पार्स करने में Node 22 में लगभग 208 मिलीसेकंड लगते हैं; जो अटकता है वह रेंडरिंग है। लाइन-उन्मुख प्रारूप लाइन टूल्स के लिए बने हैं, क्योंकि रिकॉर्ड भौतिक लाइनों से मेल खाते हैं:
# पहले पैमाना नापें: रिकॉर्ड संख्या और फ़ाइल आकार, बिना सामग्री पढ़े
wc -l events.jsonl && du -h events.jsonl
# देखें एक रिकॉर्ड कैसा दिखता है (फ़ील्ड और संरचना की पुष्टि करें)
head -n 1 events.jsonl | jq .
# ज़रूरी रिकॉर्ड सीधे खोजें: लाइन टूल्स आसानी से चलते हैं — JSONL का विशेषाधिकार
grep '"level":"error"' events.jsonl | head -n 5
tail -n 100 events.jsonl | jq 'select(.latency_ms > 500)'
अंतिम कमांड पर ध्यान दें: tail से निकलने वाली हर लाइन अब भी स्वतंत्र JSON मान है, इसलिए ऐरे दोबारा बनाए बिना वह सीधे jq में जा सकती है। यह तरीका कई GB की फ़ाइलों पर भी लगभग शून्य मेमोरी ओवरहेड के साथ उसी तरह चलता है — यही सिद्धांत बड़ी JSON फ़ाइलें पेजों को धीमा क्यों करती हैं में बताया गया है।
3. लाइन-दर-लाइन पढ़ने के तीन मानक पैटर्न
लाइन-दर-लाइन पढ़ने को तीन चीज़ें सँभालनी होती हैं: खाली लाइनों को छोड़ना (खासकर अंतिम न्यूलाइन), लाइन नंबर के साथ त्रुटियाँ रिपोर्ट करना, और इस बात की परवाह न करना कि एक खराब लाइन पूरा काम न रोक दे। तीनों भाषाओं के मानक पैटर्न इन्हीं बिंदुओं के इर्द-गिर्द घूमते हैं।
Python, केवल स्टैंडर्ड लाइब्रेरी:
import json
with open("events.jsonl", encoding="utf-8") as f:
for line_number, line in enumerate(f, start=1):
if not line.strip(): # खाली लाइनें छोड़ें
continue
try:
record = json.loads(line) # हर लाइन को स्वतंत्र रूप से पार्स करें
except json.JSONDecodeError as e:
print(f"लाइन {line_number} विफल: {e}") # रिपोर्ट करें, रुकें नहीं
continue # खराब लाइन को पृथक करें
handle(record)
Node बिल्ट-इन readline मॉड्यूल के साथ — फ़ाइल आकार की परवाह किए बिना स्थिर मेमोरी:
import fs from "node:fs";
import readline from "node:readline";
const input = fs.createReadStream("events.jsonl", "utf8");
const lines = readline.createInterface({ input, crlfDelay: Infinity });
let lineNumber = 0;
for await (const line of lines) {
lineNumber += 1;
if (!line.trim()) continue;
try {
handle(JSON.parse(line));
} catch (e) {
console.error(`लाइन ${lineNumber}: ${e.message}`);
}
}
Go 1.27 में encoding/json/v2 स्थिर डिफ़ॉल्ट बन जाता है, और उसका मान-दर-मान स्ट्रीम डिकोडिंग JSONL से पूरी तरह मेल खाता है; jq एक्सप्रेशन एम्बेड करने के लिए gojq इस्तेमाल करें। लाइब्रेरी के मामले में जल्दबाज़ी में पैकेज इंस्टॉल न करें: पायथन की jsonlines विफल लाइन का नंबर और कच्चा टेक्स्ट रिपोर्ट करती है, और orjsonl gzip/bzip2/zstd संपीड़ित फ़ाइलें सीधे पढ़ती और लिखती है; Node का ndjson पैकेज 2020 से कोई रिलीज़ नहीं कर रहा है — बिल्ट-इन readline ही काफ़ी है।
4. JSON ऐरे के साथ दोनों दिशाओं में रूपांतरण
दिशा मायने रखती है; उलट दी तो हर कमांड गलत होगी। एक नियम याद रखें: ऐरे → JSONL का मतलब है map (हर रिकॉर्ड एक लाइन में सिकुड़ा), JSONL → ऐरे का मतलब है collect (लाइनें एक सूची में जुटाई गईं)।
# JSON ऐरे → JSONL: -c हर तत्व को एक लाइन में सिकोड़ता है
jq -c '.[]' records.json > records.jsonl
# JSONL → JSON ऐरे: -s पूरा इनपुट एक ऐरे में जोड़ता है
jq -s '.' events.jsonl > records.json
# JSONL को लाइन-दर-लाइन फ़िल्टर करें: -s की ज़रूरत नहीं, jq इनपुट को स्ट्रीम की तरह प्रोसेस करता है
jq -c 'select(.active == true)' events.jsonl > active.jsonl
पायथन वन-लाइनर समतुल्य हैं: ऐरे → JSONL हर रिकॉर्ड पर json.dumps(r, ensure_ascii=False) + "\n" लिखता है; उलटी दिशा हर लाइन के json.loads को सूची में जोड़ती है। हर दिशा में एक जाल है। जाने की दिशा (ऐरे → JSONL) में रिकॉर्ड के अंदर की हर असली न्यूलाइन json.dumps से अवश्य एस्केप होनी चाहिए — वरना एक रिकॉर्ड कई लाइनों में फट जाएगा। लौटने की दिशा (JSONL → ऐरे) में लाइनों को एक विशाल स्ट्रिंग में जोड़कर पार्स न करें — लाखों लाइनों पर यह मेमोरी में एक अनावश्यक पूर्ण प्रतिलिपि है। एकत्रीकरण (योग, समूहन, औसत) के लिए -s चाहिए, जो सब लोड करता है; कई GB की फ़ाइलों पर awk या पायथन के स्ट्रीमिंग एक्यूम्युलेटर पर आ जाएँ।
5. खराब लाइनों के छह प्रकार और उनकी पहचान
लगभग हर JSONL त्रुटि लाइन स्तर पर रहती है। लाइन नंबर बताते हुए लाइन-दर-लाइन सत्यापन ही एकमात्र भरोसेमंद निदान है। आवृत्ति के क्रम में:
| लक्षण | मूल कारण | सुधार |
|---|---|---|
लाइन के अंत में Extra data |
लाइनों के बीच कॉमा जोड़ दिए गए | हटा दें; हर लाइन खुद पर खरी उतरनी चाहिए |
| एक रिकॉर्ड कई लाइनों में बँट गया | रिकॉर्ड के अंदर बिना एस्केप की असली न्यूलाइन (हाथ से फ़ॉर्मेट किया) | json.dumps / jq -c से दोबारा सिकोड़ें |
पहली लाइन Unexpected token ï के साथ विफल |
फ़ाइल में BOM है | BOM रहित UTF-8 में फिर से सहेजें |
| केवल अंतिम लाइन पर त्रुटि, हालाँकि सही दिखती है | अंतिम न्यूलाइन गायब या अतिरिक्त खाली स्थान | \n जोड़ें; परिपाटी से खाली लाइनें छोड़ दें |
| स्ट्रिंग के भीतर न्यूलाइन की त्रुटि | स्ट्रिंग के भीतर शाब्दिक न्यूलाइन, \n नहीं |
जनरेटर ठीक करें; JSON स्ट्रिंग कभी हाथ से न जोड़ें |
| बड़े पूर्णांकों का मान बदल जाता है | double से पार्स (2^53 पर कटौती) | परिशुद्धता बनाए रखने वाला पार्सर इस्तेमाल करें, देखें bigint परिशुद्धता |
निदान की विधि हमेशा तीन चरणों की है: head -n 1 से एनकोडिंग और पहली लाइन की संरचना की पुष्टि करें; ऐसा लाइन-दर-लाइन स्क्रिप्ट चलाएँ जो पहली विफलता पर रुकने की बजाय हर खराब लाइन का नंबर बताए; फिर खराब लाइनों को एक-एक करके निकालें — एक लाइन एक पूर्ण रिकॉर्ड होता है, उसे अलग जाँचने में कुछ खर्च नहीं होता। बैच इम्पोर्ट में "रोकना नहीं, क्वारंटाइन करना" नीति अनुशंसित है: खराब लाइनें उनके नंबर सहित errors.jsonl में लिखें, अच्छे रिकॉर्ड लोड करते रहें, और खराब लाइनों के अनुपात के आधार पर पुनः इम्पोर्ट का निर्णय लें।
6. HTTP पर JSONL: लाइन-दर-लाइन उपभोग और बैकप्रेशर
HTTP पर NDJSON का सामान्य ढाँचा है: सर्वर लगातार लिखता है, क्लाइंट लगातार उपभोग करता है। क्लाइंट साइड पर response.json() न कॉल करें — वह पार्स से पहले पूरे बॉडी को बफ़र करती है, जिससे स्ट्रीमिंग का अर्थ ही खो जाता है। बॉडी के स्ट्रीम को सीधे पढ़ें, \n पर काटें, और अंत का अधूरा हिस्सा अगले पुनरावृत्ति में ले जाएँ:
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
const lines = buffer.split("\n");
buffer = lines.pop(); // आखिरी टुकड़ा अधूरी लाइन हो सकता है
for (const line of lines) {
if (line.trim()) handle(JSON.parse(line));
}
}
सर्वर साइड पर दो बातें अनिवार्य हैं: पहली, जब res.write() false लौटाए तो drain घटना की प्रतीक्षा करें (बैकप्रेशर), वरना जब भी उपभोक्ता उत्पादक से धीमा हो मेमोरी असीमित बढ़ेगी; दूसरी, स्ट्रीमिंग रूटों पर gzip मिडलवेयर बंद करें — gzip कुछ भी कंप्रेस करने से पहले पूरा रिस्पॉन्स बफ़र करता है, जो NDJSON स्ट्रीमिंग को पूरी तरह बेअसर कर देता है। दोनों व्यवहार स्थानीय परीक्षण और कई स्वतंत्र अंग्रेज़ी स्रोतों से पुष्ट हैं, अनुमान नहीं।
7. कब JSONL गलत पसंद है
प्रारूप चयन का नकारात्मक पक्ष भी मायने रखता है। तीन परिदृश्य JSON ऐरे के साथ ही रहने चाहिए:
| आवश्यकता | JSONL क्यों ठीक से नहीं जुड़ता | इसकी जगह इस्तेमाल करें |
|---|---|---|
| पूरे दस्तावेज़ की संरचना का सत्यापन | समग्र संरचना नहीं है; फ़ील्ड का विचलन नज़र बच जाता है | JSON ऐरे + Schema |
| Nवें रिकॉर्ड तक सीधी पहुँच | लाइन की लंबाई बदलती रहती है; बाइनरी खोज संभव नहीं | SQLite / Parquet |
| गहरी नेस्टिंग, रिकॉर्ड के बीच संदर्भ | हर लाइन पृथक है; संदर्भों को रहने की जगह नहीं | मानक JSON दस्तावेज़ |
एक वाक्य का अनुभवजन्य नियम: अगर रिकॉर्ड समय के साथ आते हैं या लाइन-दर-लाइन उपभोग होते हैं (लॉग, इवेंट, एक्सपोर्ट, ट्रेनिंग सेट), तो JSONL लें; अगर छोटा डेटा एक बार बनता है, एक बार उपभोग होता है और पूरे दस्तावेज़ की अर्थव्यवस्था चाहिए, तो JSON ऐरे लें। व्युत्पन्न प्रारूप (RS परिसीमक वाले JSON Text Sequences) अलग विषय हैं — देखें RFC 7464 लेख।
FAQ
JSONL और NDJSON में कोई वास्तविक अंतर है?
इंजीनियरिंग के लिहाज़ से नहीं। दोनों का अर्थ "हर लाइन में एक पूर्ण JSON मान, बाहरी आवरण नहीं, लाइनों के बीच कोई कॉमा नहीं" है, और जो टूल एक को समर्थन देता है वह दूसरे को भी देता है। अंतर नामकरण और इकोसिस्टम की आदतों में है: डेटा इंजीनियरिंग और ML डेटासेट में .jsonl हावी है; स्ट्रीमिंग HTTP API में .ndjson और application/x-ndjson हावी हैं। बाहरी सिस्टम से एकीकरण करते समय उसके दस्तावेज़ में बताए एक्सटेंशन और media type का पालन करें।
क्या .jsonl फ़ाइल को सीधे JSON.parse से पढ़ सकते हैं?
पूरी फ़ाइल को नहीं। JSON.parse ठीक एक JSON टेक्स्ट स्वीकार करता है, जबकि JSONL लाइनों में सजाए गए अनेक स्वतंत्र मान होते हैं, इसलिए पूरी फ़ाइल का पार्स हमेशा "Unexpected token" या "Extra data" के साथ विफल होगा। लाइनों में बाँटें और हर गैर-खाली लाइन को अलग पार्स करें, विफलता पर लाइन नंबर रिपोर्ट करें।
अगर JSONL फ़ाइल की एक लाइन खराब है, तो क्या पूरी फ़ाइल बर्बाद हो गई?
नहीं — लाइन-स्तरीय दोष-पृथक्करण इस प्रारूप की डिज़ाइन का केंद्र है। खराब लाइन केवल अपने आप को नुकसान पहुँचाती है: उसे छोड़ दें, नंबर नोट करें और पढ़ना जारी रखें। बैच इम्पोर्ट में खराब लाइनों को अलग फ़ाइल में क्वारंटाइन करें, अच्छे रिकॉर्ड सामान्य तरीके से लोड करें, और खराब लाइनों के अनुपात के आधार पर सुधार और पुनः इम्पोर्ट का निर्णय लें।
JSONL को Excel या CSV में कैसे बदलें?
पहले उसे ऐरे में बदलें या लाइन-दर-लाइन फ़ील्ड निकालें, फिर तालिका रूपांतरण चलाएँ: jq -c '.[]' data.jsonl से सिकोड़कर कन्वर्टर को दें, या हर लाइन को json.loads से pandas तक पहुँचाएँ। सावधान: नेस्टेड फ़ील्ड को सपाट करने पर डॉट वाले कॉलम नाम बनते हैं, ऐरे फ़ील्ड सेल के भीतर केवल टेक्स्ट के रूप में सहेजे जा सकते हैं, और लंबी ID कॉलम की परिशुद्धता बचाने के लिए उन्हें शुरू से ही स्ट्रिंग घोषित करना बेहतर है।