JSON Toolkit

JSON টেক্সট সিকোয়েন্স (RFC 7464): RS দিয়ে সীমাবদ্ধ স্ট্রিমিং JSON

সংক্ষেপে: RFC 7464 একটি 0x1E বিভাজক ব্যবহার করে এমন JSON টেক্সট স্ট্রিম করে যা একসঙ্গে পড়া ও লেখা যায়। নিয়ম, NDJSON থেকে পার্থক্য, আর ভাগ-করে-পার্স করার বাস্তবায়ন এখানে।

মধ্যবর্তী~9 মিনিট পড়াপ্রকাশ 2026-10-31আপডেট 2026-10-31
JSON টেক্সট সিকোয়েন্স ও RFC 7464 রেকর্ড সেপারেটরের কভার

মূল কথা: দুই প্রান্তই আপনার নিয়ন্ত্রণে থাকলে, নিবন্ধিত মিডিয়া টাইপ দরকার হলে এবং ট্রাঙ্কেশনের পর পাঠকের পুনঃসমন্বয় দরকার হলে JSON-seq ব্যবহার করুন; ডেটা লগ সিস্টেমে যায়, মানুষ-পাঠযোগ্য হতে হবে, বা বিদ্যমান টুলের সঙ্গে চলতে হবে — তাহলে NDJSON ব্যবহার করুন। শুধু লাইনভিত্তিক ইনপুট বোঝে এমন সহকর্মী/সার্ভারে JSON-seq চাপিয়ে দেবেন না।

NDJSON নিউলাইনে রেকর্ড ভাগ করে, কিন্তু নিউলাইন একটি মানবীয় প্রথা। RFC 7464 সীমানাটি বাইট স্ট্রিমেই লিখে দেয়: প্রতিটি রেকর্ডের আগে একটি অদৃশ্য 0x1E।

১. একটি বাইট যে সমস্যা সমাধান করে

RFC 7464 ফেব্রুয়ারি ২০১৫-তে JSON Text Sequences নামে প্রকাশিত হয় এবং application/json-seq মিডিয়া টাইপ নিবন্ধ করে। এটি কোনো নতুন ডেটা টাইপ যোগ করে না। এটি কেবল ঠিক করে কীভাবে একগুচ্ছ JSON টেক্সট একটি বাইট স্ট্রিমের ভেতর সাজানো থাকে। পুরো নিয়ম একটি বাক্যে ধরে: প্রতিটি JSON টেক্সটের আগে একটি RS এবং পরে একটি LF।

  • RS হলো ASCII রেকর্ড সেপারেটর, কোড পয়েন্ট 0x1E, ইউনিকোডে INFORMATION SEPARATOR TWO (U+001E);
  • LF হলো লাইন ফিড, 0x0A।

দুটি রেকর্ড বাইট স্তরে এরকম দেখায়:

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 রেকর্ডের আগে বসে, যা NDJSON-এর "কন্টেন্ট লিখুন, তারপর ডেলিমিটার লিখুন" বিন্যাসের ঠিক উল্টো। এই দিকটাই পুনরুদ্ধারের আচরণ বদলায়: যে পাঠক 0x1E দেখে সে জানে আগের রেকর্ড শেষ হয়েছে, তাই লেখার মাঝপথে কাটা পড়া রেকর্ডও পরের রেকর্ডের সীমানা ঘোলা করতে পারে না।

এটি কোনো তাত্ত্বিক নকশা নয়। RFC স্পষ্টভাবে উল্লেখ করে ক্র্যাশ বা প্রশাসনিক কিলের পর ফাইলসিস্টেম কর্তৃক কাটা পড়া লগ ফাইলে অ্যাপেন্ড করা রেকর্ডের কথা, আর বলে যে পার্সার এ কারণে পুরো পাঠ বাতিল করা উচিত নয়।

২. দুই নিয়ম সেট: এনকোডার ও পার্সার

RFC 7464 অস্বাভাবিকভাবে দুটি ABNF ব্যাকরণ দেয় — একটি লেখকদের সংযত করে, আরেকটি পাঠকদের শিথিল করে।

ভূমিকা ব্যাকরণ অর্থ
এনকোডার *(RS JSON-text LF) প্রতিটি JSON টেক্সটের আগে একটি RS, পরে একটি LF
পার্সার *(1*RS possible-JSON) পরপর RS বাইট অনুমোদিত, আর রেকর্ডের মাঝের LF ঐচ্ছিক

ইঞ্জিনিয়ারিং ভাষায় পাঁচটি কঠোর নিয়ম আছে:

  1. UTF-8 বাধ্যতামূলক; UTF-16 ও UTF-32 ব্যবহার করা যাবে না;
  2. পার্সার "RS ছাড়া যেকোনো বাইট"-কে সম্ভাব্য JSON টেক্সট ধরে পার্স করতে চেষ্টা করে; ব্যর্থ হলে পরের RS-এ লাফ দিয়ে চালিয়ে যায়;
  3. পরপর RS বাইট খালি এলিমেন্ট বোঝায় না এবং উপেক্ষা করা হয়;
  4. সিকোয়েন্স-শেষ চিহ্ন নেই, তাই স্ট্রিম কেবল শেষ হয়;
  5. টপ-লেভেল ভ্যালু সংখ্যা, true, false বা null হলে রেকর্ডটি একটি হোয়াইটস্পেস বাইট দিয়ে শেষ হতে হবে, নাহলে সম্ভাব্য ট্রাঙ্কেটেড ধরে বাদ দেওয়া হয়।

পাঁচ নম্বর নিয়মটাই মানুষ ভুলে যায়। অবজেক্ট, অ্যারে ও স্ট্রিং JSON-এ স্ব-সীমাবদ্ধ; সংখ্যা নয়। <RS>123<RS> বলতে পারে না এটি সম্পূর্ণ 123 নাকি কাটা 1234। তাই RFC লেখকদের LF যোগ করতে বলে, যা ট্রাঙ্কেশন শনাক্তের ক্যানারি বাইট হিসেবে কাজ করে। একইভাবে <RS>true<RS> মূলত trueish হয়তো ছিল।

৩. JSON-seq ও NDJSON-এর মধ্যে বাছাই

দুটিই স্ট্রিম করে, তবে ভিন্ন সমস্যা লক্ষ্য করে:

মাত্রা JSON টেক্সট সিকোয়েন্স NDJSON / JSON Lines
ডেলিমিটারের অবস্থান রেকর্ডের আগে RS (0x1E) রেকর্ডের পরে LF (0x0A)
মিডিয়া টাইপ application/json-seq, IANA-নিবন্ধিত অফিশিয়াল নিবন্ধন নেই, কমিউনিটি প্রথা
বাইট পাঠযোগ্যতা অদৃশ্য কন্ট্রোল ক্যারেক্টার, cat কিছু দেখায় না প্লেইন টেক্সট, সরাসরি পাঠযোগ্য
সমবর্তী অ্যাপেন্ড পাঠক পুনঃসমন্বয় করে কাটা লেজ বাদ দিতে পারে অ্যাপেন্ডও কাজ করে, খারাপ লাইন এড়িয়ে যায়
টপ-লেভেল সংখ্যা বিশ্বাস করতে শেষে হোয়াইটস্পেস বাইট দরকার লাইন ফিডই সীমানা
স্ট্রিংয়ের ভেতরের ডেলিমিটার RS কেবল \u001e হিসেবে আসতে পারে LF \n হিসেবে লিখতে হবে
টুলিং jq --seq ও কয়েকটি লাইব্রেরি প্রায় সব লগ ও বিগ-ডেটা টুল
ফাইল এক্সটেনশন RFC নির্ধারণ করেনি .jsonl / .ndjson

সিদ্ধান্ত সংক্ষিপ্ত: দুই প্রান্ত আপনার নিয়ন্ত্রণে থাকলে, আনুষ্ঠানিক মিডিয়া টাইপ চাইলে এবং ট্রাঙ্কেশনের পর পাঠকের পুনঃসমন্বয় দরকার হলে JSON-seq ব্যবহার করুন; ডেটা লগ সিস্টেমে গেলে, মানুষ-পাঠযোগ্য হতে হলে, বা বিদ্যমান টুলের সঙ্গে চলতে হলে JSON Lines ও NDJSON ব্যবহার করুন।

৪. একটি পার্সার: 0x1E-এ ভাগ করুন, তারপর JSON.parse

RFC 7464-এর সবচেয়ে বাস্তব সুবিধা হলো স্ট্রিমিং JSON পার্সারের দরকার নেই। বাইটে ভাগ করুন, তারপর প্রতিটি অংশে JSON.parse কল করুন:

/** ASCII record separator, defined by RFC 7464 as 0x1E */
const RECORD_SEPARATOR = 0x1e;

/**
 * Parses a chunk of JSON Text Sequence bytes.
 * @param buffer Bytes that have been read so far
 * @returns Successfully parsed records and the number of failed 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 };
}

বাস্তবায়নের তিনটি নোট:

  • বাইট-স্তরের বিভাজন নিরাপদ। UTF-8 কন্টিনিউয়েশন বাইট 0x80–0xBF সীমায় থাকে, তাই কখনো 0x1E-এর সমান হতে পারে না। 0x1E-এ ভাগ করলে মাল্টি-বাইট অক্ষর মাঝখানে কাটা যায় না।
  • স্ট্রিমিংয়ে লেজ বাফার করুন। চাঙ্কে চাঙ্কে পড়ার সময় শেষ অংশটি হয়তো এখনও পরের RS-এ পৌঁছায়নি। পরের চাঙ্ক না আসা পর্যন্ত এটি বাফারে রাখুন; নাহলে একই আধা-রেকর্ড দুইবার পার্স হবে।
  • বাতিল না করে ব্যর্থতা গণনা করুন। RFC পার্সারকে পরের RS-এ লাফ দিয়ে চালিয়ে যেতে বলে, তাই badCount একটি সতর্কতা মেট্রিক, এক্সসেপশন নয়। কাটা পড়া শেষ রেকর্ডও ওই ঝুড়িতেই পড়ে।

মেমরি ব্যবহার সবচেয়ে বড় একক রেকর্ড প্লাস একটি বাফারের উপর নির্ভর করে, মোট ফাইল আকারের উপর নয়। এটি বড় JSON পার্সিং ও স্ট্রিমিং-এ আলোচিত একই ধারণা।

৫. স্ট্রিংয়ের ভেতরের কন্ট্রোল ক্যারেক্টার

JSON U+0000 থেকে U+001F পর্যন্ত প্রতিটি কন্ট্রোল ক্যারেক্টারকে স্ট্রিংয়ের ভেতরে এস্কেপ করতে বলে, আর RS ওই সীমাতেই পড়ে। তাই একটি বৈধ JSON স্ট্রিং কেবল পাঁচ অক্ষরের \u001e ধারণ করতে পারে, কখনো কাঁচা 0x1E বাইট নয়। এজন্যই বৈধ ডেটায় 0x1E-এ ভাগ করা সবসময় নিরাপদ।

বিপদ লেখকের দিকে থাকে:

  1. '\x1e' দিয়ে হাতে JSON টেক্সট বানানো, স্ট্রিংয়ের ভেতরে কাঁচা কন্ট্রোল ক্যারেক্টার ঢোকানো;
  2. JSON.stringify-এর বদলে টেমপ্লেট লিটেরাল দিয়ে JSON বানানো, নিউলাইন, কোট ও কন্ট্রোল ক্যারেক্টার এস্কেপ না করা;
  3. টার্মিনাল বা এডিটর থেকে RS-যুক্ত টেক্সট কপি করা, যেখানে কন্ট্রোল ক্যারেক্টার গিলে ফেলা বা প্রতিস্থাপিত হয়।

সঠিক প্যাটার্ন সবসময় আগে টেক্সট তৈরি করা, তারপর ডেলিমিটার জোড়া লাগানো:

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

JSON.stringify কন্ট্রোল ক্যারেক্টারকে \u00XX হিসেবে প্রকাশ করে, তাই রেকর্ডের ভেতরে কাঁচা RS কখনো আসতে পারে না। এই ধাপটি সঠিক হলে বিভাজনের লজিককে কেবল বাইট সামলাতে হয়, JSON সিনট্যাক্স বোঝার দরকার নেই।

এই শ্রেণির বেশিরভাগ ব্যর্থতা JSON.parse-এর Unexpected token হিসেবে প্রকাশ পায়, বিশেষত বিভ্রান্তিকর ক্ষেত্রে যেখানে "প্রথম অক্ষরটি একটি অদৃশ্য কন্ট্রোল বাইট"। JSON এরর মেসেজ লুকআপ টেবিল সেই শ্রেণির এরর লাইন ধরে ধরে ব্যাখ্যা করে।

৬. HTTP ট্রান্সপোর্ট ও মিডিয়া টাইপ

application/json-seq একটি নিবন্ধিত মিডিয়া টাইপ, যার কোনো বাধ্যতামূলক বা ঐচ্ছিক প্যারামিটার নেই, আর ক্যারেক্টার সেট UTF-8-এ স্থির। HTTP-তে তিনটি অভ্যাস গুরুত্বপূর্ণ:

  • Accept: application/json-seq পাঠান এবং Content-Type: application/json-seq দিয়ে সাড়া দিন;
  • সত্যিকারভাবে স্ট্রিম করতে সার্ভারকে chunked ট্রান্সফার ব্যবহার করতে হবে এবং রিভার্স প্রক্সি, CDN ও লোড-টেস্টিং টুলের মতো মধ্যস্থতাকারীর রেসপন্স বাফারিং বন্ধ করতে হবে;
  • চাঙ্কে চাঙ্কে পড়া ক্লায়েন্ট এমন চাঙ্ক পেতে পারে যা রেকর্ডের মাঝখানে শেষ হয়, তাই পার্সারকে পরের RS পর্যন্ত বাফার করে তবেই রেকর্ড প্রকাশ করতে হবে।

অন্য প্রান্ত কেবল লাইনভিত্তিক ইনপুট বোঝে হলে JSON-seq চাপিয়ে দেবেন না। এটি একটি স্ট্যান্ডার্ড, কিন্তু ইকোসিস্টেমে এর বিস্তার NDJSON-এর অনেক নিচে, তাই সামঞ্জস্য প্রোটোকল আলোচনার সময়েই ঠিক করা উচিত, পার্স ব্যর্থতার পরে নয়।

৭. কমান্ড লাইন, লগ ও টুল সমর্থন

jq নেটিভ সমর্থনযুক্ত কয়েকটি টুলের একটি; এর --seq ফ্ল্যাগ RFC 7464-এর জন্যই আছে:

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

এটি 1 ও 2 আলাদা লাইনে দেখায়। স্ট্রিমিং এন্ডপয়েন্টের জন্য curl-এর বাফারিং বন্ধ করতে ভুলবেন না:

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

jq --seq RFC 7464 ইনপুট পড়ে, খারাপ রেকর্ড উপেক্ষা করে এবং পার্সিং চালিয়ে যায়। এটি বিশেষভাবে উপকারী যখন এখনও লেখা চলছে এমন লগ ফাইল tail করছেন: শেষ রেকর্ড আধা-লেখা হতে পারে, আর আউটপুটে ভুয়া ট্রেইলিং অবজেক্ট দেখাবে না।

৭.১ পরিবেশ অনুযায়ী নেটিভ সমর্থন

পরিবেশ বিল্ট-ইন সমর্থন নোট
jq --seq সবচেয়ে সহজ কমান্ড-লাইন অপশন
Node.js নেই Buffer.indexOf প্লাস JSON.parse
Python নেই b'\x1e'-এ ভাগ করুন
Go নেই bufio.Scanner-এর জন্য নিজস্ব SplitFunc
ব্রাউজার নেই fetch প্লাস ReadableStream, হাতে বাইট ভাগ করুন

৭.২ সাধারণ ফাঁদ

ভুলগুলো পড়ার দিকে জমা হয়:

  1. readline দিয়ে পড়া, যা প্রথম লাইনে অদৃশ্য RS প্রিফিক্স রেখে দেয়, ফলে JSON.parse সবসময় ব্যর্থ হয়;
  2. এডিটরে .json-seq ফাইল খোলা যেখানে কন্ট্রোল ক্যারেক্টার অদৃশ্য, তাই মনে হয় এটি একটি অদ্ভুত JSON ডকুমেন্ট;
  3. split(/\n/) দিয়ে ভাগ করা, যা রেকর্ডের ভেতরের এস্কেপ করা নিউলাইন ও রেকর্ডের মাঝের LF-কে একাকার করে;
  4. ডকুমেন্টেশনে RS-কে সাধারণ অক্ষর ধরা, ফলে কপি-পেস্টে তা হারিয়ে যায়।

৮. কখন ব্যবহার করা মূল্যবান

ভালো মিল:

  • উৎপাদক ও ভোক্তা—দুটিই আপনার, মাঝে কোনো তৃতীয়-পক্ষ টুল নেই;
  • ডেটা অবিরাম অ্যাপেন্ড হওয়া ইভেন্ট স্ট্রিম আর প্রসেস ক্র্যাশ করতে পারে, তাই ট্রাঙ্কেশন থেকে পুনরুদ্ধার দরকার;
  • প্রোটোকল .jsonl-এর মতো প্রথার চেয়ে আনুষ্ঠানিক মিডিয়া টাইপের যোগ্য;
  • একক রেকর্ড টপ-লেভেল সংখ্যা বা বুলিয়ান হতে পারে, যেখানে স্পষ্ট ট্রাঙ্কেশন নিয়ম গুরুত্বপূর্ণ।

খারাপ মিল:

  • লগ ELK, Loki বা শুধু লাইনভিত্তিক ইনপুট বোঝে এমন অন্য সিস্টেমে যাবে;
  • মানুষকে সরাসরি cat, grep ও awk করতে হবে;
  • অন্য প্রান্ত কেবল application/json পার্স করতে পারে।

৯. চেকলিস্ট

  • [ ] লেখক JSON.stringify দিয়ে রেকর্ড তৈরি করে, তারপর 0x1E প্রিফিক্স ও \n সাফিক্স যোগ করে;
  • [ ] টপ-লেভেল সংখ্যা, বুলিয়ান বা null ধারণকারী রেকর্ড LF দিয়ে শেষ হয়;
  • [ ] পাঠক লাইন ধরে পড়ার বদলে বাইটে ভাগ করে;
  • [ ] স্ট্রিমিং পাঠ অসম্পূর্ণ লেজ পরের RS আসা পর্যন্ত বাফারে রাখে;
  • [ ] পার্স ব্যর্থতা গণনা করা ও এড়িয়ে যাওয়া হয়, কখনো সিকোয়েন্সের জন্য মারাত্মক নয়;
  • [ ] পাবলিক ইন্টারফেস স্পষ্টভাবে application/json-seq ঘোষণা করে এবং NDJSON ফলব্যাক রাখে।

সাধারণ প্রশ্ন

পাবলিক API এন্ডপয়েন্টে JSON-seq গ্রহণ করা উচিত?

কেবল যখন দুই প্রান্তই বদলানো যায় বা ফরম্যাট আগেই আলোচনা করা যায়। JSON-seq application/json-seq মিডিয়া টাইপসহ নিবন্ধিত স্ট্যান্ডার্ড, কিন্তু ইকোসিস্টেমে এর বিস্তার NDJSON-এর অনেক নিচে, তাই তৃতীয়-পক্ষ ক্লায়েন্টে চাপিয়ে দিলে সাধারণত ব্যর্থ হয়। প্রোটোকল আলোচনার সময়েই সামঞ্জস্য ঠিক করুন এবং NDJSON ফলব্যাক রাখুন।

বিদ্যমান NDJSON পাইপলাইনকে JSON-seq-এ রূপান্তর করা উচিত?

সাধারণত না। NDJSON ইতিমধ্যে স্ট্রিম করে, অ্যাপেন্ড করে ও খারাপ লাইন এড়িয়ে যায়, আর প্রায় সব লগ ও বিগ-ডেটা টুল এটি গ্রহণ করে। কেবল যখন IANA-নিবন্ধিত মিডিয়া টাইপ বা সামনে ডেলিমিটার যে পুনঃসমন্বয় দেয় তা দরকার, এবং উৎপাদক ও ভোক্তা একসঙ্গে বদলানো সম্ভব, তখনই রূপান্তর করুন।

কোনটি বেশি প্রচলিত, JSON টেক্সট সিকোয়েন্স নাকি NDJSON?

NDJSON। এর নিয়ম সরল, টুল কভারেজ অনেক বিস্তৃত, আর লগ সিস্টেম ও বিগ-ডেটা পাইপলাইন সাধারণত এটি গ্রহণ করে। JSON-seq IANA-নিবন্ধিত মিডিয়া টাইপে এবং ডেলিমিটার আগে রাখার ফলে পাওয়া পুনঃসমন্বয়ে এগিয়ে, যা নিয়ন্ত্রিত স্ট্রিমিং প্রোটোকলের জন্য উপযুক্ত, সাধারণ ডেটা বিনিময়ের জন্য নয়।

ডেলিমিটার রেকর্ডের পরে নয়, আগে কেন?

কারণ রেকর্ড কাটা পড়তে পারে। সামনের ডেলিমিটারে, RS দেখা পাঠক জানে নতুন রেকর্ড শুরু হয়েছে, তাই আগের রেকর্ড সম্পূর্ণ হোক বা না হোক, পরেরটিকে নষ্ট করতে পারে না। পিচ্ছের ডেলিমিটারে, বাধাপ্রাপ্ত লেখা একটি শেষ অংশ রেখে যায় যার না শেষ-চিহ্ন আছে না পরের শুরু-চিহ্ন, তাই সীমানা কেবল অনুমান করতে হয়।

রেকর্ডের ভেতরে 0x1E এলে কী হয়?

একটি বৈধ JSON স্ট্রিং RS-কে \u001e হিসেবে এস্কেপ করতে বাধ্য, তাই কাঁচা 0x1E কখনো আসা উচিত নয়। যদি আসে, টেক্সট আর বৈধ JSON নয়: 0x1E-এ ভাগ করলে ভুল জায়গায় ভাঙবে আর JSON.parse ব্যর্থ হবে। সমাধান লেখকের দিকে: হাতে বানানো টেক্সটের বদলে JSON.stringify ব্যবহার করুন।

ব্রাউজার কি application/json-seq সরাসরি পার্স করতে পারে?

না। ব্রাউজারে JSON-seq পার্সার থাকে না; fetch কেবল বাইট পৌঁছে দেয়। response.body.getReader() ব্যবহার করতে হবে, পরের 0x1E পর্যন্ত চাঙ্ক জমা করতে হবে, এবং প্রতিটি অংশে JSON.parse করতে হবে। ছোট পেললোডে পুরো বডি আগে স্ট্রিং হিসেবে পড়ে পরে ভাগ করলেও কাজ করে।

টপ-লেভেল সংখ্যায় শেষে LF দরকার কেন?

অবজেক্ট, অ্যারে ও স্ট্রিং JSON-এ স্ব-সীমাবদ্ধ, অথচ সংখ্যা নয়: <RS>123<RS> সম্পূর্ণ 123 বা কাটা 1234 হতে পারে। তাই RFC 7464 প্রতিটি রেকর্ডের পরে একটি হোয়াইটস্পেস বাইট বাধ্যতামূলক করে, আর LF ট্রাঙ্কেশন শনাক্তের ক্যানারি হিসেবে কাজ করে। একই নিয়ম টপ-লেভেল true, false ও null-এও প্রযোজ্য।