JSON Toolkit

JSON Lines dan NDJSON: Data Penstriman

Inti pati: Satu nilai JSON per baris membeli kebolehtambahan, penstriman dan kerosakan terpencil pada satu baris; kosnya ialah ketiadaan struktur keseluruhan, jadi pengesahan mesti dilakukan baris demi baris.

Pertengahan~10 min bacaanDiterbitkan 2026-10-30Dikemas kini 2026-10-30
Kulit JSON Lines dan NDJSON data penstriman

Kesimpulan utama: gunakan tatasusunan JSON apabila anda memproses keseluruhan kelompok sekali gus, dan JSON Lines apabila anda memerlukan sisipan penstriman dan pengasingan baris rosak; jika anda masih perlu mengesahkan struktur keseluruhan, jangan tukar kepada format berdelimit baris.

Sebaik sahaja data melebihi memori, atau terus tiba secara berterusan, struktur "satu tatasusunan besar" menjadi musuh.

1. Peraturan format

JSON Lines (.jsonl) dan NDJSON (.ndjson) hampir sinonim, dan peraturannya ringkas dalam kedua-dua kes:

  1. Pengekodan ialah UTF-8, tanpa BOM;
  2. Setiap baris mengandungi satu nilai JSON lengkap dan sah;
  3. Baris dipisahkan oleh \n; baris terakhir boleh atau tidak berakhir dengan baris baru;
  4. Tiada baris baru tidak dilepaskan boleh muncul dalam satu baris — baris baru di dalam rentetan mesti ditulis sebagai \n;
  5. Baris kosong biasanya diabaikan, tetapi pelaksanaan yang ketat patut menganggapnya tidak sah.
{"time":"2026-10-30T10:41:45+08:00","level":"info","message":"boot"}
{"time":"2026-10-30T10:41:46+08:00","level":"warn","message":"slow query","costMs":812}
{"time":"2026-10-30T10:41:47+08:00","level":"error","message":"timeout","traceId":"a1b2"}

Perbezaan halus antara keduanya: laman web JSON Lines menekankan "satu nilai per baris, dan nilai itu boleh jadi JSON apa pun (termasuk skalar)", manakala spesifikasi NDJSON menambah peraturan pelaksanaan yang lebih ketat seperti "tiada baris kosong dan tiada skalar peringkat atas". Dalam kejuruteraan, mengikuti set yang lebih ketat menjauhkan anda daripada masalah.

2. Perbandingan dengan tatasusunan JSON

Dimensi Tatasusunan JSON JSON Lines
Tambah satu rekod Tulis semula keseluruhan fail (ubah ] penutup) Hanya tambah satu baris
Bacaan penstriman Memerlukan penghurai penstriman yang menjejaki kedalaman kurungan Pisahkan mengikut baris
Satu entri rosak Keseluruhan fail menjadi tidak boleh dihuraikan Hanya baris itu terjejas
Pengesahan keseluruhan fail Sahkan semuanya dalam satu pas Hanya baris demi baris
Pemampatan Saiz serupa selepas gzip Sama, dan blok boleh dimampatkan secara selari
Menyatakan bersarang Disokong secara asli Setiap baris boleh bersarang secara dalaman; baris tidak berkaitan antara satu sama lain
Pengguna tipikal Semua alat JSON Sistem log, saluran data besar, set latihan LLM

Kesimpulannya langsung: sama ada data itu "sekumpulan rekod" atau "satu dokumen" menentukan format mana yang digunakan.

3. Bacaan: penstriman baris demi baris

Dalam Node, pendekatan paling ringkas ialah readline:

import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';

/**
 * Membaca fail JSON Lines baris demi baris dan memanggil panggil balik untuk setiap rekod.
 * @param filePath Laluan ke fail
 * @param onRecord Pengendali untuk satu rekod
 * @returns Statistik yang mengandungi bilangan baris berjaya dan gagal
 */
async function readJsonLines(
  filePath: string,
  onRecord: (record: unknown, lineNumber: number) => void,
): Promise<{ okCount: number; badCount: number }> {
  const lineReader = createInterface({
    input: createReadStream(filePath, { encoding: 'utf8' }),
    crlfDelay: Infinity,
  });

  let okCount = 0;
  let badCount = 0;

  for await (const rawLine of lineReader) {
    const line = rawLine.trim();
    if (!line) {
      continue;
    }
    try {
      onRecord(JSON.parse(line), okCount + badCount + 1);
      okCount += 1;
    } catch {
      badCount += 1;
    }
  }

  return { okCount, badCount };
}

Penggunaan memori kod ini bergantung hanya pada saiz satu baris, bukan saiz keseluruhan fail — itulah nilai terbesar format berdelimit baris.

3.1 Apabila anda hanya memerlukan beberapa medan, jangan JSON.parse dahulu

Jika setiap rekod mempunyai 50 medan dan anda hanya memerlukan 3, JSON.parse membina objek untuk kesemua 50 medan itu. Penghurai penstriman (stream-json, clarinet) boleh mengekstrak hanya medan sasaran, dengan kos kerumitan pelaksanaan yang lebih tinggi. Peraturan praktikal: apabila satu rekod di bawah beberapa ratus KB, memanggil JSON.parse secara terus biasanya masih lebih berbaloi.

3.2 Teks Cina dan BOM

Fail .jsonl yang dijana pada Windows sering membawa BOM, dan JSON.parse pada baris pertama dijamin gagal. Buang BOM sebelum membaca, atau periksa tiga bait pertama semasa menstrim ketulan pertama.

4. Penulisan: sisipan dan keatomikan

import { appendFile } from 'node:fs/promises';

/**
 * Menambah satu rekod JSON Lines.
 * @param filePath Fail sasaran
 * @param record Objek rekod untuk ditulis
 * @returns Janji yang diselesaikan apabila penulisan selesai
 */
async function appendRecord(filePath: string, record: unknown): Promise<void> {
  const line = `${JSON.stringify(record)}\n`;
  await appendFile(filePath, line, { encoding: 'utf8' });
}

Dua perkara untuk diperhatikan:

  1. Output JSON.stringify tidak mengandungi baris baru (kecuali \n dalam rentetan dilepaskan menjadi dua aksara), jadi satu baris sentiasa tepat satu rekod;
  2. Apabila beberapa proses menambah secara serentak, satu write yang lebih kecil daripada PIPE_BUF (biasanya 4096 bait pada Linux) adalah atomik; apa-apa yang lebih besar tidak dijamin. Untuk senario keselumanan tinggi, tukar kepada "satu fail per proses, digabungkan selepas dihantar ke cakera", atau gunakan storan dengan transaksi.

5. Mengendalikan baris rosak

Ini ialah keputusan yang dipaksa oleh format berdelimit baris untuk dibuat lebih awal:

Strategi Sesuai untuk Kos
Langkau dan kira Data yang bertoleransi terhadap jurang, seperti log dan peristiwa analitik Data hilang secara senyap, jadi pemberitahuan adalah wajib
Gagal serta-merta Data yang tidak boleh hilang, seperti pengebilan dan invois Satu baris rosak menyekat keseluruhan kelompok
Hantar ke fail surat mati Kes yang perlu diisi semula kemudian Storan tambahan dan proses main semula

Apa pun pilihan anda, rekod nombor baris dan serpihan kandungan asal, jika tidak anda tidak boleh mencari masalah kemudian. Nombor baris tidak ada dalam mesej ralat JSON.parse, jadi anda mesti menyelenggaranya sendiri.

6. Pemampatan dan penyerpihan

  • gzip memampatkan .jsonl pada nisbah tinggi yang konsisten (nama medan berulang), jadi .jsonl.gz disyorkan;
  • gzip memampatkan dalam blok dan menyokong penyahmampatan penstriman secara asli, jadi tidak perlu menyahmampat keseluruhan fail;
  • Bahagikan fail besar kepada kepingan sekitar 1GB, dengan nombor jujukan dalam nama, untuk memudahkan pemprosesan selari dan pemindahan boleh sambung;
  • Jangan sekali-kali prapemproses .jsonl dengan "menggabungkannya menjadi tatasusunan" — itu membuang semua kelebihannya.

7. Tukar ganti berbanding format data besar lain

Format Struktur Skema disertakan Sesuai untuk
JSON Lines Bebas setiap baris Tidak (memerlukan kontrak luaran) Log, import dan eksport, data latihan
CSV Berjadual Tidak Laporan, pertukaran Excel
Parquet Berkolumnar Ya Pertanyaan analitik, set data luar talian sangat besar
Avro Berasaskan baris Ya Mesej Kafka, evolusi skema

Kelebihan JSON Lines ialah mana-mana bahasa dan alat boleh membacanya serta menulisnya serta-merta; kelemahannya ialah kekurangan skema dan pemampatan berkolumnar. Sebaik sahaja data tidak lagi muat pada satu mesin dan anda memerlukan pertanyaan berorientasikan lajur yang kerap, sudah tiba masanya untuk mempertimbangkan Parquet.

8. Senarai semak

  • [ ] Sambungan fail konsisten (.jsonl atau .ndjson) dan tidak pernah bercampur;
  • [ ] Penulis menjamin "satu rekod per baris", dengan baris baru di dalam rentetan sudah dilepaskan;
  • [ ] Pembaca membuang BOM, melangkau baris kosong, dan menyelenggara nombor baris;
  • [ ] Strategi baris rosak ditakrifkan secara eksplisit dan dipantau (baris rosak / jumlah baris);
  • [ ] Fail besar dihiris mengikut saiz dan dipasangkan dengan gzip serta bacaan selari;
  • [ ] Jika ada kontrak medan, skema tambahan disediakan dan disahkan baris demi baris.

Soalan Lazim

Bagaimana saya memilih antara tatasusunan JSON dan JSON Lines?

Lihat sama ada data itu sekumpulan rekod atau satu dokumen. Pilih JSON Lines apabila rekod ditambah dan dibaca sebagai strim: menambah bermakna menulis satu baris di hujung, dan membaca hanya memerlukan pemisahan mengikut baris. Kekalkan tatasusunan JSON apabila keseluruhan struktur mesti disahkan dalam satu pas, kerana tatasusunan JSON mengesahkan semuanya sekali gus manakala JSON Lines hanya boleh disahkan baris demi baris.

Mengapa satu baris rosak tidak merosakkan keseluruhan fail?

Setiap baris dalam JSON Lines ialah nilai JSON lengkap yang berasingan, dan penghuraian secara semula jadi baris demi baris, jadi data rosak hanya menjejaskan baris itu. Dalam tatasusunan JSON, satu ralat sintaks menjadikan keseluruhan fail tidak boleh dihuraikan, yang tepat sebabnya data berdelimit baris sesuai untuk log dan peristiwa analitik.

Patutkah saya menukar JSON Lines menjadi tatasusunan JSON?

Boleh: baca baris demi baris, panggil JSON.parse pada setiap rekod, dan kumpulkannya menjadi tatasusunan. Berbuat demikian memerlukan keseluruhan kandungan muat dalam memori dan melepaskan bacaan penstriman serta sisipan satu baris. Prapemprosesan .jsonl menjadi tatasusunan membuang semua kelebihan format itu, jadi ia hanya munasabah untuk penghantaran sekali sahaja di mana data muat dalam memori.