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.

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á:
- Kódování je UTF-8, bez BOM;
- Každý řádek nese jednu úplnou, platnou hodnotu JSON;
- Řádky se oddělují
\n; poslední řádek může nebo nemusí končit novým řádkem; - Uvnitř řádku se nesmí objevit neescapované zalomení řádku – zalomení uvnitř řetězce se musí zapsat jako
\n; - 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:
- Výstup
JSON.stringifyneobsahuje zalomení řádků (ledaže se\nuvnitř řetězce escapuje na dva znaky), takže jeden řádek je vždy přesně jeden záznam; - Když několik procesů připojuje souběžně, jediný
writemenší 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
.jsonls trvale vysokým poměrem (názvy polí se opakují), takže.jsonl.gzje 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
1GBs 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í (
.jsonlnebo.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.