JSON Toolkit

JSON Lines a NDJSON: streamovaná data

Shrnutí: Jedna hodnota JSON na řádek kupuje připojitelnost, streamování a poškození izolované na jediný řádek; cenou je absence celkové struktury, takže validace musí probíhat řádek po řádku.

Středně pokročilý~10 min čteníPublikováno 2026-10-30Aktualizováno 2026-10-30
Titulní obrázek článku JSON Lines a NDJSON pro streamovaná data

Stručně: pole JSON použijte, když zpracováváte celou dávku najednou, a JSON Lines, když potřebujete streamované připojování a izolaci špatných řádků; pokud stále potřebujete validovat celkovou strukturu, k řádkově oddělenému formátu nepřecházejte.

Jakmile data přesáhnou paměť, nebo neustále přicházejí, struktura „jednoho velkého pole“ se stane nepřítelem.

1. Pravidla formátu

JSON Lines (.jsonl) a NDJSON (.ndjson) jsou v praxi synonyma a pravidla jsou v obou případech krátká:

  1. Kódování je UTF-8, bez BOM;
  2. Každý řádek nese jednu úplnou, platnou hodnotu JSON;
  3. Řádky se oddělují \n; poslední řádek může nebo nemusí končit novým řádkem;
  4. Uvnitř řádku se nesmí objevit neescapované zalomení řádku – zalomení uvnitř řetězce se musí zapsat jako \n;
  5. Prázdné řádky se obvykle ignorují, ale striktní implementace by je měla považovat za neplatné.
{"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"}

Jemný rozdíl mezi oběma: web JSON Lines zdůrazňuje „jedna hodnota na řádek a hodnotou může být jakýkoli JSON (včetně skalárů)“, zatímco specifikace NDJSON přidává přísnější implementační pravidla, například „žádné prázdné řádky a žádné skaláry na nejvyšší úrovni“. V inženýrství vás dodržování přísnější sady udrží mimo potíže.

2. Srovnání s polem JSON

Dimenze Pole JSON JSON Lines
Připojení jednoho záznamu Přepis celého souboru (změna uzavírací ]) Jen připojení jednoho řádku
Streamované čtení Vyžaduje streamovaný parser sledující hloubku závorek Rozdělení podle řádků
Jeden poškozený záznam Celý soubor se stane neparsovatelným Ovlivněn je jen ten řádek
Validace celého souboru Validace všeho v jednom průchodu Jen řádek po řádku
Komprese Podobná velikost po gzip Stejná, a bloky lze komprimovat paralelně
Vyjádření vnoření Nativně podporováno Každý řádek může vnitřně vnořovat; řádky spolu nesouvisí
Typičtí konzumenti Všechny nástroje JSON Logovací systémy, big-data pipeline, trénovací sady LLM

Závěr je přímý: zda jsou data „dávkou záznamů“, nebo „jedním dokumentem“, rozhoduje o volbě formátu.

3. Čtení: streamování řádek po řádku

V Node je nejjednodušší přístup readline:

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

/**
 * Čte soubor JSON Lines řádek po řádku a pro každý záznam vyvolá callback.
 * @param filePath Cesta k souboru
 * @param onRecord Handler pro jeden záznam
 * @returns Statistiky s počtem úspěšných a selhavších řádků
 */
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 };
}

Spotřeba paměti tohoto kódu závisí jen na velikosti jednoho řádku, nikoli na celkové velikosti souboru – to je největší hodnota řádkově odděleného formátu.

3.1 Když potřebujete jen několik polí, neparsujte nejprve JSON.parse

Pokud má každý záznam 50 polí a potřebujete 3, JSON.parse postaví objekty pro všech 50. Streamovaný parser (stream-json, clarinet) umí vytáhnout jen cílové pole, za cenu vyšší složitosti implementace. Pravidlo palce: když je jeden záznam pod několik stovek KB, přímé volání JSON.parse je obvykle stále lepší obchod.

3.2 Čínský text a BOM

Soubory .jsonl vytvořené ve Windows často nesou BOM a JSON.parse na prvním řádku zaručeně selže. BOM odstraňte před čtením, nebo prozkoumejte první tři bajty při streamování prvního bloku.

4. Zápis: připojování a atomicita

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

/**
 * Připojí jeden záznam JSON Lines.
 * @param filePath Cílový soubor
 * @param record Zapisovaný objekt záznamu
 * @returns Promise, který se vyřeší po dokončení zápisu
 */
async function appendRecord(filePath: string, record: unknown): Promise<void> {
  const line = `${JSON.stringify(record)}\n`;
  await appendFile(filePath, line, { encoding: 'utf8' });
}

Dvě věci k pozornosti:

  1. Výstup JSON.stringify neobsahuje zalomení řádků (ledaže se \n uvnitř řetězce escapuje na dva znaky), takže jeden řádek je vždy přesně jeden záznam;
  2. Když několik procesů připojuje souběžně, jediný write menší než PIPE_BUF (obvykle 4096 bajtů na Linuxu) je atomický; cokoli většího zaručeno není. Pro scénáře s vysokou souběžností přejděte k „jeden soubor na proces, sloučený po vyprázdnění na disk“, nebo použijte úložiště s transakcemi.

5. Zpracování špatných řádků

Toto je rozhodnutí, které řádkově oddělený formát vynucuje předem:

Strategie Vhodná pro Cena
Přeskočit a počítat Data, která tolerují mezery, například logy a analytické události Data se tiše ztratí, takže alerting je povinný
Okamžitě selhat Data, která nesmějí být ztracena, například fakturace a faktury Jeden špatný řádek zablokuje celou dávku
Přesměrovat do souboru mrtvých dopisů Případy vyžadující pozdější doplnění Další úložiště a proces přehrání

Ať zvolíte cokoli, zaznamenejte číslo řádku a fragment původního obsahu, jinak problém později nelokalizujete. Číslo řádku není v chybové zprávě JSON.parse, takže si ho musíte udržovat sami.

6. Komprese a dělení

  • gzip komprimuje .jsonl s trvale vysokým poměrem (názvy polí se opakují), takže .jsonl.gz je doporučený;
  • gzip komprimuje v blocích a nativně podporuje streamovanou dekompresi, takže není třeba dekomprimovat celý soubor;
  • Rozdělte velké soubory na řezy o zhruba 1GB s pořadovými čísly v názvech, aby bylo snadnější paralelní zpracování a obnovitelné přenosy;
  • Nikdy nepředzpracovávejte .jsonl „sloučením do pole“ – tím zahodíte všechny jeho výhody.

7. Kompromisy proti jiným big-data formátům

Formát Struktura Zahrnuje schéma Vhodný pro
JSON Lines Nezávislé na řádek Ne (vyžaduje externí kontrakt) Logy, importy a exporty, trénovací data
CSV Tabulková Ne Reporty, výměna s Excelem
Parquet Sloupcová Ano Analytické dotazy, velmi velké offline datasety
Avro Řádková Ano Zprávy Kafka, vývoj schématu

Výhodou JSON Lines je, že ho každý jazyk a každý nástroj okamžitě umí číst i zapisovat; nevýhodou je absence schématu a sloupcové komprese. Jakmile se data už nevejdou na jeden stroj a potřebujete časté sloupcově orientované dotazy, je čas zvážit Parquet.

8. Kontrolní seznam

  • [ ] Přípona souboru je konzistentní (.jsonl nebo .ndjson) a nikdy se nemíchá;
  • [ ] Zapisovač zaručuje „jeden záznam na řádek“, s zalomeními řádků uvnitř řetězců už escapovanými;
  • [ ] Čtečka odstraňuje BOM, přeskakuje prázdné řádky a udržuje čísla řádků;
  • [ ] Strategie špatných řádků je explicitně definována a monitorována (špatné řádky / celkové řádky);
  • [ ] Velké soubory jsou děleny podle velikosti a párovány s gzipem a paralelním čtením;
  • [ ] Kde existuje kontrakt polí, je poskytnuto další schéma a validace řádek po řádku.

Časté dotazy

Jak volit mezi polem JSON a JSON Lines?

Podívejte se, zda jsou data dávkou záznamů, nebo jedním dokumentem. JSON Lines zvolte, když se záznamy připojují a čtou jako proud: připojení znamená zápis jednoho řádku na konec a čtení vyžaduje jen rozdělení podle řádků. Pole JSON ponechte, když celá struktura musí být validována v jednom průchodu, protože pole JSON validuje všechno najednou, zatímco JSON Lines lze validovat jen řádek po řádku.

Proč jeden poškozený řádek nerozbije celý soubor?

Každý řádek v JSON Lines je nezávislá, úplná hodnota JSON a parsování je přirozeně řádek po řádku, takže poškozená data ovlivní jen ten řádek. V poli JSON jedna syntaktická chyba činí celý soubor neparsovatelným, což je přesně důvod, proč řádkově oddělená data sedí logům a analytickým událostem.

Mám převést JSON Lines na pole JSON?

Můžete: číst řádek po řádku, volat JSON.parse pro každý záznam a sbírat je do pole. To vyžaduje, aby se celý obsah vešel do paměti, a vzdává se streamovaného čtení i připojování jednoho řádku. Předzpracování .jsonl na pole zahodí všechny výhody formátu, takže je rozumné jen pro jednorázové doručení, kde se data vejdou do paměti.