JSONL ফাইল প্র্যাকটিক্যাল গাইড: খোলা, কনভার্ট ও ত্রুটিপূর্ণ লাইন শনাক্তকরণ
সংক্ষেপে: পুরো .jsonl ফাইল JSON.parse-এ দিলে এরর আসা প্রায় কখনোই বোঝায় না যে ফাইল নষ্ট — বোঝায় পড়ার পদ্ধতি ভুল। এই গাইডে থাকছে পরীক্ষা ও লাইন ধরে পড়ার মানক প্যাটার্ন, 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, এডিটর সবশেষে
৫০০ MB-র .jsonl ডাবল ক্লিক করে এডিটরে খোলা সবচেয়ে সাধারণ ভুল: ত্রিশ লাখ লাইন টেক্সটের সাথে সিনট্যাক্স হাইলাইট আর আনডু বাফার যোগ হলে প্রায় যেকোনো এডিটর আটকে যাবে — আর এর দায় JSON-এর নয়। ৪৫ MB ডকুমেন্ট পার্স করতে Node 22-এ লাগে প্রায় ২০৮ মিলিসেকেন্ড; আটকে যায় রেন্ডারিং। লাইন-ভিত্তিক ফরম্যাট লাইন টুলের জন্যই বানানো, কারণ রেকর্ড আর ফিজিক্যাল লাইন একই জায়গায় থাকে:
# প্রথমে মাপটা নিন: রেকর্ড সংখ্যা ও ফাইল সাইজ, কনটেন্ট না পড়েই
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-তে পাঠানো যায়। কয়েক গিগাবাইট ফাইলেও প্রায় শূন্য মেমরি খরচে একই পদ্ধতি কাজ করে — এটিই বড় 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 ব্যবহার করুন। লাইব্রেরির ব্যাপারে দ্রুত প্যাকেজ ইনস্টল করতে যাবেন না: Python-এর jsonlines ব্যর্থ লাইনের নম্বর ও মূল টেক্সট জানায়, আর orjsonl gzip/bzip2/zstd সংকুচিত ফাইল সরাসরি পড়ে ও লেখে; Node-এর ndjson প্যাকেজ ২০২০ সালের পর কোনো রিলিজ দেয়নি — বিল্ট-ইন 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
Python ওয়ান-লাইনারও সমান: অ্যারে → JSONL প্রতি রেকর্ডে json.dumps(r, ensure_ascii=False) + "\n" লেখে; উল্টো দিকে প্রতি লাইনে json.loads করে তালিকায় জমা হয়। প্রতিটি দিকের একটি করে ফাঁদ আছে। যাওয়ার দিকে (অ্যারে → JSONL) রেকর্ডের ভেতরের যেকোনো সত্যিকারের নিউলাইন json.dumps দিয়ে অবশ্যই এস্কেপ করতে হবে — নইলে একটি রেকর্ড একাধিক লাইনে ছেঁড়া পড়বে। ফেরার দিকে (JSONL → অ্যারে) লাইনগুলো জোড়া দিয়ে বিশাল এক স্ট্রিং বানিয়ে পার্স করবেন না — লাখো লাইনে এটি মেমরিতে এক অপ্রয়োজনীয় পূর্ণ কপি। এগ্রিগেশন (যোগফল, গ্রুপিং, গড়) লাগলে -s দরকার, যা সব লোড করে; কয়েক GB-র ফাইলে awk বা Python-এর স্ট্রিমিং অ্যাকুমুলেটরে সরে যান।
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 কলাম প্রিসিশন রক্ষায় আগেই স্ট্রিং হিসেবে ঘোষণা করা উচিত।