JSON Toolkit

ใช้งานไฟล์ JSONL จริง: เปิด แปลง และแก้บรรทัดเสีย

สรุปสำคัญ: โยนไฟล์ .jsonl ทั้งไฟล์ให้ JSON.parse แล้วพบ error แทบไม่เคยหมายความว่าไฟล์เสีย — แต่หมายถึงวิธีอ่านผิด บทความนี้ครอบคลุมวิธีมาตรฐานในการตรวจสอบและอ่าน JSONL ทีละบรรทัด คำสั่งแปลงสองทางกับอาร์เรย์ JSON ตารางบรรทัดเสียหกประเภท และวิธีที่ถูกต้องในการบริโภค NDJSON จาก HTTP response แบบสตรีม

ระดับกลางอ่านประมาณ 9 นาทีเผยแพร่ 2026-11-06อัปเดต 2026-11-06
ปกคู่มือปฏิบัติการอ่านไฟล์ JSONL ทีละบรรทัดและการแยกบรรทัดที่เสีย

ใจความสำคัญ: แต่ละบรรทัดของไฟล์ JSONL เป็นค่า JSON ที่ถูกต้องและเป็นอิสระจากกัน แต่ทั้งไฟล์ไม่ใช่เอกสาร JSON ชิ้นเดียว ถ้ายัดทั้งไฟล์ให้ JSON.parse หรือ json.load แล้วล้มเหลว เก้าในสิบครั้งไฟล์ไม่ได้เสีย — วิธีอ่านของคุณต่างหากที่ผิด

หนึ่งเรกคอร์ดต่อบรรทัด ไม่มีคอมมาระหว่างบรรทัด ไม่มีอาร์เรย์ครอบนอก — JSONL ยอมสละ "โครงสร้างของทั้งเอกสาร" เพื่อแลกกับการเขียนต่อท้าย การอ่านแบบสตรีม และการกักขังความเสียหายไว้ที่บรรทัดเดียว ข้อดีทั้งสามจะเกิดจริงเฉพาะเมื่อประมวลผลไฟล์ทีละบรรทัด และทั้งสามกลายเป็น error ทันทีที่พยายาม parse ทั้งไฟล์ในครั้งเดียว

1. สามชื่อ หนึ่งรูปแบบ: JSONL, NDJSON, JSON Lines

มือใหม่มักคิดว่าชื่อต่างกันแปลว่าไวยากรณ์ต่างกัน ไม่ใช่:

ชื่อ บริบทที่ใช้บ่อย นามสกุล / media type
JSON Lines วิศวกรรมข้อมูล ชุดข้อมูล ML (pandas, Spark, Hugging Face) .jsonl
NDJSON HTTP API แบบสตรีม ข้อมูลเรียลไทม์ .ndjson, application/x-ndjson
JSONL ชื่อเรียกกันทั่วไปสำหรับทั้งสอง —

เครื่องมือที่อ่านได้อันหนึ่งย่อมอ่านได้อีกอันหนึ่ง เกณฑ์เดียวในการเลือกชื่อคือธรรมเนียมของระบบนิเวศปลายทางของคุณ: เขียน .jsonl เมื่อเตรียมชุดข้อมูลให้ Hugging Face ตั้ง application/x-ndjson เมื่อเปิด API แบบสตรีม ไฟล์ JSONL ที่พกพาได้ต้องผ่านสามกฎ: เข้ารหัส UTF-8 (ไม่มี BOM) หนึ่งค่า JSON สมบูรณ์ต่อบรรทัด และบรรทัดคั่นด้วย \n การมีบรรทัดว่างท้ายไฟล์ได้รับการแนะนำอย่างยิ่ง ส่วนบรรทัดว่างไม่ใช่เรกคอร์ดที่ถูกต้อง รายละเอียดโปรโตคอลและเหตุผลเชิงออกแบบมีใน บทความภาพรวม JSON Lines และ NDJSON ของเรา บทความนี้พูดถึงสิ่งที่ต้องทำเมื่อไฟล์อยู่ในมือแล้วเท่านั้น

2. การตรวจสอบ: head ก่อน แล้ว grep ส่วน editor ไว้ท้ายสุด

ดับเบิลคลิกไฟล์ .jsonl ขนาด 500 MB เพื่อเปิดใน editor คือความผิดพลาดที่พบบ่อยที่สุด: ข้อความสามล้านบรรทัดบวกการไฮไลต์ไวยากรณ์บวกบัฟเฟอร์ undo จะทำให้ editor แทบทุกตัวค้าง — และ JSON ไม่ได้ผิด การ parse เอกสาร 45 MB ใช้เวลาราว 208 ms ใน 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. สามรูปแบบมาตรฐานของการอ่านทีละบรรทัด

การอ่านทีละบรรทัดต้องจัดการสามเรื่อง: ข้ามบรรทัดว่าง (โดยเฉพาะบรรทัดสุดท้าย) รายงาน error พร้อมเลขบรรทัด และไม่ให้บรรทัดเสียเพียงบรรทัดเดียวทำให้งานทั้งหมดล้ม รูปแบบมาตรฐานของสามภาษาล้วนหมุนรอบสามประเด็นนี้

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) # parse แต่ละบรรทัดแยกกัน
        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 ใน Python รายงานเลขบรรทัดและข้อความดิบของทุกบรรทัดที่พลาด และ orjsonl อ่านเขียนไฟล์บีบอัด gzip/bzip2/zstd ได้โดยตรง ส่วนแพ็กเกจ ndjson ของ Node ไม่ได้ปล่อยเวอร์ชันใหม่ตั้งแต่ปี 2020 — readline ในตัวก็พอ

4. การแปลงสองทางกับอาร์เรย์ JSON

ทิศทางสำคัญ กลับด้านเมื่อไรคำสั่งพังทั้งหมด จดกฎเดียว: อาร์เรย์ → JSONL คือ map (บีบเรกคอร์ดแต่ละตัวเป็นหนึ่งบรรทัด) ส่วน JSONL → อาร์เรย์คือ collect (เก็บบรรทัดต่าง ๆ รวมเป็นลิสต์)

# อาร์เรย์ JSON → JSONL: -c บีบแต่ละองค์ประกอบเป็นหนึ่งบรรทัด
jq -c '.[]' records.json > records.jsonl

# JSONL → อาร์เรย์ JSON: -s รวมทั้ง input เป็นอาร์เรย์เดียว
jq -s '.' events.jsonl > records.json

# กรอง JSONL ทีละบรรทัด: ไม่ต้องใช้ -s, jq ประมวลผล input เป็นสตรีม
jq -c 'select(.active == true)' events.jsonl > active.jsonl

คำสั่งบรรทัดเดียวของ Python เทียบเท่ากัน: อาร์เรย์ → JSONL เขียน json.dumps(r, ensure_ascii=False) + "\n" ต่อเรกคอร์ด ทิศกลับรวมผล json.loads ทีละบรรทัดเป็นลิสต์ แต่ละทิศมีกับดักของตัวเองหนึ่งอัน ขาไป (อาร์เรย์ → JSONL) อักขระขึ้นบรรทัดจริงภายในเรกคอร์ดต้องถูก escape โดย json.dumps เสมอ — ไม่งั้นเรกคอร์ดหนึ่งตัวจะถูกฉีกเป็นหลายบรรทัด ขากลับ (JSONL → อาร์เรย์) อย่าเอาบรรทัดทั้งหมดมาต่อกันเป็นสตริงยักษ์แล้ว parse — กับหลายล้านบรรทัดนั่นคือการคัดลอกทั้งหมดเข้าหน่วยความจำโดยไม่จำเป็น การรวมค่า (ผลรวม จัดกลุ่ม ค่าเฉลี่ย) ต้องใช้ -s ซึ่งโหลดทุกอย่าง บนไฟล์หลาย GB ให้เปลี่ยนไปใช้ awk หรือตัวสะสมแบบสตรีมใน Python

5. บรรทัดเสียหกประเภทและวิธีวินิจฉัย

เกือบทุก error ของ JSONL เกิดในระดับบรรทัด การตรวจทีละบรรทัดพร้อมรายงานเลขบรรทัดคือการวินิจฉัยที่เชื่อถือได้เพียงหนทางเดียว เรียงตามความถี่:

อาการ สาเหตุราก วิธีแก้
Extra data ท้ายบรรทัด ใส่คอมมาระหว่างบรรทัด ลบออก แต่ละบรรทัดต้องยืนได้ด้วยตัวเอง
เรกคอร์ดหนึ่งถูกฉีกเป็นหลายบรรทัด อักขระขึ้นบรรทัดจริงไม่ได้ escape ในเรกคอร์ด (จัดรูปแบบด้วยมือ) บีบใหม่ด้วย json.dumps / jq -c
บรรทัดแรกล้มด้วย Unexpected token ï ไฟล์มี BOM บันทึกใหม่เป็น UTF-8 แบบไม่มี BOM
เฉพาะบรรทัดสุดท้าย error ทั้งที่ดูถูกต้อง ขาดบรรทัดว่างท้ายไฟล์หรือมีช่องว่างแปลกปลอม เติม \n ตามธรรมเนียมให้ข้ามบรรทัดว่าง
error จากอักขระขึ้นบรรทัดในสตริง มีอักขระขึ้นบรรทัดจริงในสตริง ไม่ใช่ \n แก้ตัวสร้างข้อมูล ห้ามต่อสตริง JSON ด้วยมือ
เลขจำนวนเต็มใหญ่เปลี่ยนค่า parse ผ่าน double (ตัดที่ 2^53) ใช้ parser ที่รักษาความแม่นยำ ดู ความแม่นยำ bigint

ขั้นตอนวินิจฉัยมีสามขั้นเสมอ: head -n 1 เพื่อยืนยันการเข้ารหัสและโครงสร้างบรรทัดแรก รันสคริปต์แบบทีละบรรทัดที่รายงานเลขทุกบรรทัดเสียแทนการหยุดที่บรรทัดแรก จากนั้นดึงบรรทัดเสียออกมาดูทีละบรรทัด — หนึ่งบรรทัดคือหนึ่งเรกคอร์ดสมบูรณ์ การส่องแยกจึงไม่มีต้นทุน สำหรับการนำเข้าเป็นชุด แนวนโยบายที่แนะนำคือ "กักกัน ไม่ใช่หยุดทั้งหมด": เขียนบรรทัดเสียพร้อมเลขบรรทัดลง errors.jsonl โหลดเรกคอร์ดที่ดีต่อไป แล้วตัดสินใจเรื่องนำเข้าซ้ำจากสัดส่วนบรรทัดเสีย

6. JSONL บน HTTP: การบริโภคทีละบรรทัดและ backpressure

รูปแบบคลาสสิกของ NDJSON บน HTTP คือเซิร์ฟเวอร์เขียนต่อเนื่อง ลูกข่ายบริโภคต่อเนื่อง ฝั่งลูกข่ายอย่าเรียก response.json() — มันบัฟเฟอร์ทั้ง body ก่อน parse ทำให้ความหมายของสตรีมหายไป อ่านสตรีมของ body โดยตรง ตัดที่ \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 (backpressure) ไม่งั้นหน่วยความจำจะโตไม่มีที่สิ้นสุดทุกครั้งที่ผู้บริโภคช้ากว่าผู้ผลิต สอง ปิด middleware gzip บนเส้นทางสตรีม — gzip ต้องบัฟเฟอร์ทั้ง response ก่อนบีบอัดอะไรได้ ซึ่งทำให้สตรีมของ NDJSON ไร้ความหมายทั้งหมด พฤติกรรมทั้งสองยืนยันด้วยการทดสอบในเครื่องและแหล่งข้อมูลอิสระภาษาอังกฤษหลายฉบับ ไม่ใช่การเดา

7. กรณีที่ JSONL เป็นทางเลือกที่ผิด

มุมมองด้านลบของการเลือกรูปแบบก็สำคัญ สามสถานการณ์นี้ควรอยู่กับอาร์เรย์ JSON:

ความต้องการ เหตุใด JSONL จึงเข้าไม่ดี ใช้แทนด้วย
ตรวจสอบโครงสร้างของทั้งเอกสาร ไม่มีโครงสร้างรวม การเลื่อนของฟิลด์มองไม่เห็น อาร์เรย์ JSON + Schema
เข้าถึงเรกคอร์ดที่ N โดยตรง ความยาวบรรทัดไม่คงที่ หาแบบไบนารีไม่ได้ SQLite / Parquet
ซ้อนลึก การอ้างอิงข้ามเรกคอร์ด แต่ละบรรทัดอิสระจากกัน การอ้างอิงไม่มีที่อยู่ เอกสาร JSON มาตรฐาน

กฎหนึ่งประโยค: ถ้าเรกคอร์ดมาตามเวลาหรือถูกบริโภคทีละบรรทัด (ล็อก เหตุการณ์ การส่งออก ชุดเทรน) ใช้ JSONL ถ้าข้อมูลชุดเล็กสร้างครั้งเดียว ใช้ครั้งเดียว และต้องการความหมายของทั้งเอกสาร ใช้อาร์เรย์ JSON รูปแบบที่พัฒนาต่อ (JSON Text Sequences ที่ใช้ตัวคั่น RS) เป็นเรื่องอื่น — ดู บทความ RFC 7464

FAQ

JSONL กับ NDJSON ต่างกันจริงหรือไม่?

ในเชิงวิศวกรรมไม่ต่าง ทั้งคู่หมายถึง "หนึ่งค่า JSON สมบูรณ์ต่อบรรทัด ไม่มีตัวห่อนอก ไม่มีคอมมาระหว่างบรรทัด" และเครื่องมือที่รองรับอันหนึ่งย่อมรองรับอีกอันหนึ่ง ต่างกันที่ชื่อเรียกและธรรมเนียมของระบบนิเวศ: .jsonl ครองพื้นที่วิศวกรรมข้อมูลและชุดข้อมูล ML ส่วน .ndjson และ application/x-ndjson ครองพื้นที่ HTTP API แบบสตรีม เมื่อเชื่อมต่อกับระบบภายนอกให้ทำตามนามสกุลและ media type ที่เอกสารของระบบนั้นระบุ

อ่านไฟล์ .jsonl ด้วย JSON.parse ตรง ๆ ได้ไหม?

ทั้งไฟล์ไม่ได้ JSON.parse รับข้อความ JSON เพียงชิ้นเดียวเท่านั้น ขณะที่ JSONL คือค่าอิสระจำนวนมากเรียงกันเป็นบรรทัด การ parse ทั้งไฟล์จึงล้มเหลวเสมอด้วย "Unexpected token" หรือ "Extra data" ให้แยกเป็นบรรทัดแล้ว parse ทีละบรรทัดที่ไม่ว่าง พลาดเมื่อไรรายงานเลขบรรทัด

ถ้าบรรทัดหนึ่งในไฟล์ JSONL เสีย ทั้งไฟล์ใช้ไม่ได้หรือ?

ไม่ — การกักขังความเสียหายไว้ที่ระดับบรรทัดคือหัวใจของการออกแบบรูปแบบนี้ บรรทัดเสียกระทบแค่ตัวเอง: ข้ามไป จดเลขบรรทัดไว้ แล้วอ่านต่อ สำหรับการนำเข้าเป็นชุด ให้แยกบรรทัดเสียไปไว้ในไฟล์กักกัน โหลดเรกคอร์ดที่ดีตามปกติ แล้วตัดสินใจแก้ไขและนำเข้าซ้ำจากสัดส่วนบรรทัดเสีย

แปลง JSONL เป็น Excel หรือ CSV อย่างไร?

ก่อนอื่นเปลี่ยนเป็นอาร์เรย์หรือดึงฟิลด์ออกทีละบรรทัด แล้วรันการแปลงเป็นตาราง: บีบด้วย jq -c '.[]' data.jsonl แล้วส่งให้ตัวแปลง หรือส่งแต่ละบรรทัดผ่าน json.loads เข้า pandas ระวัง: ฟิลด์ซ้อนกันที่ถูกแผ่ออกจะได้ชื่อคอลัมน์แบบมีจุด ฟิลด์แบบอาร์เรย์จะเก็บในเซลล์ได้เพียงข้อความ และคอลัมน์ ID ยาวควรประกาศเป็นสตริงตั้งแต่ต้นเพื่อไม่ให้ความแม่นยำหายไป

แหล่งอ้างอิง

เครื่องมือที่เกี่ยวข้อง

บทความที่เกี่ยวข้อง

JSON Lines และ NDJSON: ข้อมูลแบบ Streaming

หนึ่งค่า JSON ต่อบรรทัดแลกมาด้วยความสามารถในการต่อท้าย การ streaming และความเสียหายที่จำกัดอยู่เพียงบรรทัดเดียว ต้นทุนคือการไม่มีโครงสร้างโดยรวม การตรวจสอบจึงต้องทำทีละบรรทัด

ระดับกลางอ่านประมาณ 10 นาทีอัปเดต 2026-10-30

JSON Text Sequences (RFC 7464): JSON แบบ Streaming ที่คั่นด้วย RS

RFC 7464 ใช้ตัวคั่น 0x1E เพื่อ stream JSON text ที่อ่านและเขียนได้พร้อมกัน ครอบคลุมกฎ ความแตกต่างจาก NDJSON และ implementation ที่แยกก่อนแล้วจึง parse

ระดับกลางอ่านประมาณ 9 นาทีอัปเดต 2026-10-31

การแยกวิเคราะห์ การเรนเดอร์ และการสตรีม JSON ขนาดใหญ่

การแยกวิเคราะห์มักไม่ใช่คอขวด แต่การเรนเดอร์และการคัดลอกคือคอขวด ปรับแต่งตามสี่ชั้น "แยกวิเคราะห์ → ไล่ → เรนเดอร์ → ขนส่ง" เพื่อไม่ให้ความพยายามสูญเปล่า

ระดับกลางอ่านประมาณ 13 นาทีอัปเดต 2026-10-31