JSON Toolkit

JSON Text Sequences (RFC 7464): RS से सीमांकित streaming JSON

मुख्य बात: RFC 7464 एक 0x1E separator से JSON texts stream करता है जिन्हें आप एक साथ पढ़ और लिख सकते हैं। नियम, NDJSON से अंतर, और split-फिर-parse implementation शामिल हैं।

मध्यम~9 मिनट पढ़ें2026-10-31 को प्रकाशित2026-10-31 को अपडेट किया गया
JSON Text Sequences और RFC 7464 record separator का कवर

सार: JSON-seq तब इस्तेमाल करें जब आप दोनों सिरों पर नियंत्रण रखते हों, पंजीकृत media type चाहिए हो, और reader को truncation के बाद resynchronise करना हो; NDJSON तब इस्तेमाल करें जब डेटा log सिस्टम में जाता हो, मानव-पठनीय होना चाहिए, या मौजूदा उपकरणों के साथ काम करना हो — केवल line-delimited input समझने वाले साथी पर JSON-seq कभी न थोपें।

NDJSON records को newlines पर बाँटता है, पर newline मानवीय परंपरा है। RFC 7464 सीमा को byte stream में ही लिख देता है: हर record से पहले एक अदृश्य 0x1E।

1. एक byte से हल होने वाली समस्या

RFC 7464 फ़रवरी 2015 में JSON Text Sequences शीर्षक के साथ प्रकाशित हुआ, और यह application/json-seq media type पंजीकृत करता है। यह कोई नया data type नहीं जोड़ता। यह केवल परिभाषित करता है कि एक byte stream के भीतर JSON texts का क्रम कैसे व्यवस्थित होता है। पूरा नियम एक वाक्य में आ जाता है: हर JSON text से पहले एक RS और उसके बाद एक LF रखें।

  • RS ASCII record separator है, code point 0x1E, Unicode में INFORMATION SEPARATOR TWO (U+001E) के रूप में ज्ञात;
  • LF line feed है, 0x0A.

दो records byte स्तर पर ऐसे दिखते हैं:

1E 7B 22 69 64 22 3A 31 7D 0A      ← 0x1E {"id":1} + LF
1E 7B 22 69 64 22 3A 32 7D 0A      ← 0x1E {"id":2} + LF

RS record के पहले बैठता है, जो NDJSON के "सामग्री लिखो, फिर delimiter लिखो" लेख से उलटा है। वह दिशा recovery व्यवहार बदल देती है: जो reader 0x1E देखता है वह जान लेता है कि पिछला record खत्म हो गया, इसलिए लिखाई के बीच टूटा record भी अगले की सीमा धुँधली नहीं कर सकता।

यह सैद्धांतिक डिज़ाइन नहीं है। RFC स्पष्ट रूप से log फ़ाइलों में जोड़े गए उन records का उल्लेख करता है जो crash या प्रशासनिक kill के बाद filesystem द्वारा truncate हो जाते हैं, और कहता है कि parser को इस कारण पूरी read रोकनी नहीं चाहिए।

2. दो नियम सेट: encoders और parsers

RFC 7464 असामान्य रूप से दो ABNF grammars देता है, एक writers को सीमित करता है और एक readers को ढीला करता है।

भूमिका Grammar अर्थ
Encoder *(RS JSON-text LF) हर JSON text से पहले एक RS, उसके बाद एक LF
Parser *(1*RS possible-JSON) लगातार RS bytes की अनुमति है, और records के बीच LF वैकल्पिक है

Engineering की भाषा में, पाँच कठोर नियम हैं:

  1. UTF-8 अनिवार्य है; UTF-16 और UTF-32 इस्तेमाल नहीं किए जा सकते;
  2. Parser "RS के अलावा कोई भी byte" को संभावित JSON text मानता है और उसे parse करने की कोशिश करता है; fail होने पर वह अगले RS तक छोड़कर आगे बढ़ता है;
  3. लगातार RS bytes खाली elements नहीं दर्शाते और नज़रअंदाज़ किए जाते हैं;
  4. कोई end-of-sequence marker नहीं है, इसलिए stream बस खत्म हो जाती है;
  5. जब top-level मान number, true, false या null हो, तो record को एक whitespace byte पर खत्म होना चाहिए, वरना उसे संभवतः truncated मानकर छोड़ दिया जाता है।

पाँचवाँ नियम वह है जो लोग भूलते हैं। JSON में objects, arrays और strings self-delimiting हैं; numbers नहीं हैं। <RS>123<RS> आपको यह नहीं बता सकता कि इसमें पूर्ण 123 है या truncated 1234। इसलिए RFC writers से LF जोड़ने की माँग करता है, उसे truncation detection के लिए canary byte की तरह इस्तेमाल करते हुए। इसी तरह <RS>true<RS> मूल रूप से trueish हो सकता था।

3. JSON-seq और NDJSON में चयन

दोनों formats stream करते हैं, पर अलग समस्याओं को लक्षित करते हैं:

आयाम JSON Text Sequences NDJSON / JSON Lines
Delimiter स्थिति record से पहले RS (0x1E) record के बाद LF (0x0A)
Media type application/json-seq, IANA-पंजीकृत कोई आधिकारिक पंजीकरण नहीं, समुदाय परंपरा
Byte पठनीयता अदृश्य control character, cat कुछ नहीं दिखाता सादा text, सीधे पठनीय
समवर्ती appends Reader resync कर सकता है और टूटा tail छोड़ सकता है Appends भी काम करते हैं, ख़राब पंक्तियाँ छूट जाती हैं
Top-level numbers भरोसे के लिए trailing whitespace byte चाहिए Line feed ही सीमा है
Strings के अंदर delimiter RS केवल \u001e के रूप में आ सकता है LF को \n लिखना होगा
Tooling jq --seq और कुछ लाइब्रेरी लगभग हर log और big-data उपकरण
फ़ाइल extension RFC द्वारा परिभाषित नहीं .jsonl / .ndjson

निर्णय छोटा है: JSON-seq तब इस्तेमाल करें जब आप दोनों सिरों पर नियंत्रण रखते हों, औपचारिक media type चाहिए हो, और reader को truncation के बाद resynchronise करना हो; JSON Lines और NDJSON तब इस्तेमाल करें जब डेटा log सिस्टम में जाता हो, मनुष्यों द्वारा पठनीय होना चाहिए, या मौजूदा उपकरणों के साथ काम करना हो।

4. एक parser: 0x1E पर बाँटें, फिर JSON.parse

RFC 7464 का सबसे ठोस लाभ यह है कि आपको streaming JSON parser की ज़रूरत नहीं। Byte से बाँटें, फिर हर segment पर JSON.parse बुलाएँ:

/** ASCII record separator, RFC 7464 द्वारा 0x1E के रूप में परिभाषित */
const RECORD_SEPARATOR = 0x1e;

/**
 * JSON Text Sequence bytes के एक chunk को parse करता है।
 * @param buffer अब तक पढ़े गए bytes
 * @returns सफलतापूर्वक parsed records और विफल records की संख्या
 */
function parseJsonSequence(buffer: Buffer): { records: unknown[]; badCount: number } {
  const records: unknown[] = [];
  let badCount = 0;
  let recordStart = buffer.indexOf(RECORD_SEPARATOR);

  while (recordStart !== -1) {
    const nextRecordStart = buffer.indexOf(RECORD_SEPARATOR, recordStart + 1);
    const recordEnd = nextRecordStart === -1 ? buffer.length : nextRecordStart;
    const rawText = buffer.subarray(recordStart + 1, recordEnd).toString('utf8').trim();

    if (rawText !== '') {
      try {
        records.push(JSON.parse(rawText));
      } catch {
        badCount += 1;
      }
    }

    recordStart = nextRecordStart;
  }

  return { records, badCount };
}

तीन implementation नोट्स:

  • Byte-स्तर पर बाँटना सुरक्षित है। UTF-8 continuation bytes 0x80–0xBF सीमा में रहते हैं, इसलिए वे कभी 0x1E के बराबर नहीं हो सकते। 0x1E पर बाँटना multi-byte character को आधा नहीं काट सकता।
  • Streaming करते समय tail buffer करें। Chunks में पढ़ते समय, अंतिम segment शायद अगले RS तक न पहुँचा हो। अगला chunk आने तक उसे buffer में रखें; वरना आप उसी आधे-record को दो बार parse करेंगे।
  • Abort करने के बजाय failures गिनें। RFC parsers से कहता है कि अगले RS तक छोड़कर जारी रखें, इसलिए badCount एक warning metric है, exception नहीं। Truncated अंतिम record भी उसी bucket में आता है।

Memory उपयोग सबसे बड़े एक record और एक buffer पर निर्भर है, कुल फ़ाइल आकार पर नहीं। यह वही विचार है जो बड़ी JSON का parsing और streaming में चर्चित है।

5. Strings के अंदर control characters

JSON माँगता है कि U+0000 से U+001F तक हर control character string के भीतर escaped हो, और RS उसी सीमा में आता है। इसलिए एक valid JSON string में केवल पाँच characters \u001e हो सकते हैं, कच्चा 0x1E byte कभी नहीं। इसीलिए valid डेटा के लिए 0x1E पर बाँटना हमेशा सुरक्षित है।

खतरा writer की तरफ़ रहता है:

  1. '\x1e' से हाथ से JSON text बनाना, string में कच्चा control character डालना;
  2. JSON.stringify के बजाय template literals से JSON बनाना, newlines, quotes और control characters unescaped छोड़ना;
  3. Terminal या एडिटर से RS वाला text कॉपी करना, जहाँ control character निगल लिया या बदल दिया जाता है।

सही पैटर्न हमेशा पहले text बनाना है, फिर delimiter जोड़ना:

const recordText = JSON.stringify(record);
const line = `${String.fromCharCode(RECORD_SEPARATOR)}${recordText}\n`;

JSON.stringify control characters को \u00XX के रूप में निकालता है, इसलिए raw RS record के भीतर कभी नहीं आ सकता। एक बार यह चरण सही हो, तो splitting logic को केवल bytes संभालने होते हैं और JSON syntax समझने की ज़रूरत कभी नहीं होती।

इस श्रेणी की ज़्यादातर विफलताएँ JSON.parse के Unexpected token throw करने के रूप में सामने आती हैं, विशेष रूप से वह भ्रमित करने वाला मामला जहाँ "पहला character एक अदृश्य control byte है"। JSON error संदेश lookup table उस वर्ग की error को पंक्ति-दर-पंक्ति समझाती है।

6. HTTP transport और media type

application/json-seq एक पंजीकृत media type है जिसके कोई required या optional parameters नहीं, और character set UTF-8 पर निश्चित है। HTTP पर, तीन प्रथाएँ मायने रखती हैं:

  • Accept: application/json-seq भेजें और Content-Type: application/json-seq के साथ उत्तर दें;
  • वास्तव में stream करने के लिए, server को chunked transfer इस्तेमाल करना चाहिए और reverse proxies, CDNs तथा load-testing tools जैसे intermediaries में response buffering बंद करना चाहिए;
  • Chunk-दर-chunk पढ़ने वाला client ऐसा chunk पा सकता है जो record के बीच में खत्म हो, इसलिए parser को record निकालने से पहले अगले RS तक buffer करना चाहिए।

अगर दूसरा पक्ष केवल line-delimited input समझता है, तो उस पर JSON-seq न थोपें। यह मानक है, पर उसकी ecosystem penetration NDJSON से कहीं कम है, इसलिए compatibility protocol negotiation के दौरान तय होनी चाहिए, parse failure के बाद नहीं।

7. Command line, logs और tool समर्थन

jq उन गिने-चुने उपकरणों में है जिनके पास मूल समर्थन है; उसका --seq flag ठीक RFC 7464 के लिए है:

printf '\x1e{"id":1}\n\x1e{"id":2}\n' | jq --seq -c '.id'

यह 1 और 2 अलग-अलग पंक्तियों पर छापता है। Streaming endpoint के लिए, curl की buffering बंद करना याद रखें:

curl -sN https://api.example.com/events | jq --seq -c '.type'

jq --seq RFC 7464 input पढ़ता है, ख़राब records नज़रअंदाज़ करता है, और parsing जारी रखता है। यह विशेष रूप से ऐसी log फ़ाइल tail करते समय उपयोगी है जिसमें अब भी लिखा जा रहा है: अंतिम record आधा लिखा हो सकता है, और आउटपुट में नकली trailing object नहीं दिखेगा।

7.1 वातावरण के अनुसार मूल समर्थन

वातावरण अंतर्निहित समर्थन नोट्स
jq --seq सबसे आसान command-line विकल्प
Node.js कोई नहीं Buffer.indexOf और JSON.parse
Python कोई नहीं b'\x1e' पर बाँटें
Go कोई नहीं bufio.Scanner के लिए कस्टम SplitFunc
Browser कोई नहीं fetch और ReadableStream, bytes हाथ से बाँटें

7.2 सामान्य नुकसान

गलतियाँ पढ़ने के पक्ष में झुंड बनाती हैं:

  1. readline से पढ़ना, जो पहली पंक्ति पर अदृश्य RS prefix छोड़ देता है इसलिए JSON.parse हमेशा fail होता है;
  2. .json-seq फ़ाइल को ऐसे एडिटर में खोलना जहाँ control characters अदृश्य हैं, इसलिए वह एक अजीब JSON document जैसी दिखती है;
  3. split(/\n/) से बाँटना, जो record के भीतर escaped newlines को records के बीच LF से मिला देता है;
  4. Documentation में RS को सामान्य character मानना, इसलिए copy-paste के बाद वह गायब हो जाता है।

8. कब इस्तेमाल करना फ़ायदेमंद है

अच्छे मेल:

  • Producer और consumer दोनों आपके हैं, बीच में कोई third-party उपकरण नहीं;
  • डेटा लगातार जोड़ा जाने वाला event stream है और process crash हो सकता है, इसलिए truncation से recovery चाहिए;
  • Protocol को .jsonl जैसी परंपरा के बजाय औपचारिक media type का हकदार माना जाता है;
  • अलग-अलग records top-level numbers या booleans हो सकते हैं, जहाँ स्पष्ट truncation नियम मायने रखता है।

ख़राब मेल:

  • Logs को ELK, Loki या ऐसे किसी सिस्टम में जाना है जो केवल line-delimited input समझता है;
  • लोगों को डेटा सीधे cat, grep और awk करना है;
  • दूसरा पक्ष केवल application/json parse कर सकता है।

9. जाँच सूची

  • [ ] Writer JSON.stringify से records बनाता है, फिर 0x1E पहले और \n बाद में जोड़ता है;
  • [ ] Top-level numbers, booleans या null वाले records LF पर खत्म होते हैं;
  • [ ] Reader पंक्ति-दर-पंक्ति पढ़ने के बजाय bytes पर बाँटता है;
  • [ ] Streaming reads अधूरा tail अगले RS आने तक buffer में रखते हैं;
  • [ ] Parse failures गिने और छोड़े जाते हैं, sequence के लिए कभी fatal नहीं;
  • [ ] Public interfaces application/json-seq स्पष्ट रूप से घोषित करते हैं और NDJSON fallback रखते हैं।

सामान्य प्रश्न

क्या public API endpoint के लिए JSON-seq अपनाना चाहिए?

केवल तब जब दोनों सिरे बदले जा सकें या format पहले से negotiate हो। JSON-seq application/json-seq media type वाला पंजीकृत मानक है, पर उसकी ecosystem penetration NDJSON से कहीं कम है, इसलिए तीसरे पक्ष के client पर थोपना आमतौर पर fail होता है। Protocol negotiation के दौरान compatibility तय करें और NDJSON fallback रखें।

क्या मौजूदा NDJSON pipeline को JSON-seq में बदलना चाहिए?

आमतौर पर नहीं। NDJSON पहले से stream, append और ख़राब पंक्तियाँ छोड़ता है, और लगभग हर log व big-data उपकरण उसे स्वीकार करता है। केवल तब बदलें जब आपको IANA-पंजीकृत media type चाहिए या leading delimiter से मिलने वाला resynchronisation चाहिए, और केवल तब जब producer और consumer साथ बदले जा सकें।

कौन अधिक आम है, JSON Text Sequences या NDJSON?

NDJSON। उसके नियम सरल हैं, tool coverage कहीं व्यापक है, और log सिस्टम व big-data pipelines आमतौर पर उसे स्वीकार करते हैं। JSON-seq IANA-पंजीकृत media type और delimiter पहले रखने से मिलने वाले resynchronisation पर जीतता है, जो सामान्य डेटा आदान-प्रदान के बजाय नियंत्रित streaming protocols के लिए उपयुक्त है।

Delimiter record के बाद के बजाय पहले क्यों रखा गया है?

क्योंकि records truncate हो सकते हैं। Leading delimiter के साथ, जो reader RS देखता है वह जान लेता है कि नया record शुरू हुआ, इसलिए पिछला record अगले को ख़राब नहीं कर सकता, चाहे वह पूरा हो या न हो। Trailing delimiter के साथ, रुकी हुई write एक अंतिम segment छोड़ती है जिसके पास न अंत marker है न आगे शुरुआत marker, इसलिए सीमा का अनुमान ही लगाया जा सकता है।

अगर record के अंदर 0x1E आ जाए तो क्या होता है?

एक valid JSON string को RS \u001e के रूप में escape करना ही होता है, इसलिए raw 0x1E कभी नहीं आना चाहिए। अगर आता है, तो text valid JSON नहीं रहता: 0x1E पर बाँटना गलत जगह टूटेगा और JSON.parse fail होगा। समाधान writer की तरफ़ है: हाथ से बनाए text के बजाय JSON.stringify इस्तेमाल करें।

क्या browser सीधे application/json-seq parse कर सकता है?

नहीं। Browsers कोई JSON-seq parser नहीं देते; fetch केवल bytes पहुँचाता है। आपको response.body.getReader() इस्तेमाल करना होगा, अगले 0x1E तक chunks इकट्ठा करने होंगे, और हर segment पर JSON.parse बुलाना होगा। छोटे payloads के लिए, पहले पूरा body string के रूप में पढ़कर बाद में बाँटना भी काम करता है।

Top-level number को trailing LF क्यों चाहिए?

JSON में objects, arrays और strings self-delimiting हैं, जबकि numbers नहीं: <RS>123<RS> पूर्ण 123 या truncated 1234 हो सकता है। इसलिए RFC 7464 हर record के बाद whitespace byte माँगता है, और LF truncation detection के लिए canary का काम करता है। वही नियम top-level true, false और null पर लागू है।