Práce s JSONL soubory: otevření, konverze a odhalení vadných řádků
Shrnutí: Předat celý soubor .jsonl do JSON.parse a dostat chybu téměř nikdy neznamená, že je soubor rozbitý — znamená to, že strategie čtení je špatná. Tento průvodce pokrývá standardní způsoby kontroly a čtení JSONL po řádcích, příkazy pro převod do a z JSON polí, tabulku šesti typů vadných řádků a správný způsob zpracování NDJSON ze streamované HTTP odpovědi.

V kostce: každý řádek JSONL souboru je samostatná platná JSON hodnota, ale soubor jako celek není jediný JSON dokument. Když selže předání celého souboru do JSON.parse nebo json.load, v devíti případech z deseti je soubor v pořádku — špatně je vaše strategie čtení.
Jeden záznam na řádek, žádné čárky mezi řádky, žádné vnější pole — JSONL se vzdává „struktury celého dokumentu" a získává za to přidávání záznamů, streamované čtení a izolaci chyb na úrovni jednoho řádku. Všechny tři výhody se projeví jen tehdy, když soubor zpracováváte řádek po řádku, a všechny tři se promění v chyby ve chvíli, kdy se snažíte parsovat celý soubor najednou.
1. Tři názvy, jeden formát: JSONL, NDJSON, JSON Lines
Začátečníci často považují rozdíl názvů za rozdíl v syntaxi. Není v něm:
| Název | Typický kontext | Přípona / media type |
|---|---|---|
| JSON Lines | Datové inženýrství, ML datasety (pandas, Spark, Hugging Face) | .jsonl |
| NDJSON | Streamované HTTP API, data v reálném čase | .ndjson, application/x-ndjson |
| JSONL | Běžná zkratka pro kterýkoli z obou | — |
Každý nástroj, který umí číst jeden formát, umí i druhý. Jediným kritériem volby názvu jsou konvence vašeho ekosystému: pište .jsonl, když připravujete datasety pro Hugging Face, nastavte application/x-ndjson, když provozujete streamované API. Přenositelný JSONL soubor splňuje tři pravidla: kódování UTF-8 (bez BOM), jedna úplná JSON hodnota na řádek, řádky oddělené \n. Závěrečný konec řádku na konci souboru je silně doporučen a prázdné řádky nejsou platné záznamy. Detaily protokolu a zdůvodnění návrhu pokrývá náš přehled JSON Lines a NDJSON; tento článek se striktně věnuje tomu, co dělat, když už soubor máte.
2. Kontrola: nejdřív head, pak grep, editor až na konci
Dvojklik na 500 MB .jsonl, abyste ho otevřeli v editoru, je nejčastější chyba: tři miliony řádků textu plus zvýrazňování syntaxe plus buffer pro vracení změn zaseknou prakticky jakýkoli editor — a JSON k tomu nemá co mluvit. Parsování 45 MB dokumentu trvá v Node 22 asi 208 ms; zamrzá až vykreslování. Formáty orientované na řádky jsou stvořené pro řádkové nástroje, protože záznamy se překrývají s fyzickými řádky:
# Nejdřív odhadněte rozsah: počet záznamů a velikost souboru, aniž byste četli obsah
wc -l events.jsonl && du -h events.jsonl
# Podívejte se, jak záznam vypadá (potvrďte pole a strukturu)
head -n 1 events.jsonl | jq .
# Najděte cílové záznamy přímo: řádkové nástroje prostě fungují — privilegium JSONL
grep '"level":"error"' events.jsonl | head -n 5
tail -n 100 events.jsonl | jq 'select(.latency_ms > 500)'
Všimněte si posledního příkazu: každý řádek na výstupu z tail je stále samostatná JSON hodnota, takže může jít rovnou do jq bez nutnosti znovu stavět pole. Tento přístup funguje stejně na souborech o několika gigabajtech s téměř nulovou paměťovou režií — je to stejný princip jako v proč velké JSON soubory zpomalují stránky.
3. Tři standardní vzory čtení po řádcích
Čtení po řádcích musí zvládnout tři věci: přeskočit prázdné řádky (zejména závěrečný konec řádku), hlásit chyby s číslem řádku a nikdy nedovolit, aby jeden vadný řádek zabil celou práci. Standardní vzory ve všech třech jazycích se točí kolem těchto bodů.
Python, jen se standardní knihovnou:
import json
with open("events.jsonl", encoding="utf-8") as f:
for line_number, line in enumerate(f, start=1):
if not line.strip(): # přeskočit prázdné řádky
continue
try:
record = json.loads(line) # parsovat každý řádek nezávisle
except json.JSONDecodeError as e:
print(f"Řádek {line_number} selhal: {e}") # nahlásit, nepřerušit
continue # izolovat vadný řádek
handle(record)
Node s vestavěným modulem readline, konstantní paměť bez ohledu na velikost souboru:
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(`Řádek ${lineNumber}: ${e.message}`);
}
}
V Go 1.27 se encoding/json/v2 stává stabilním výchozím modulem, jehož streamové dekódování po hodnotách přesně sedí na JSONL; pro vložené jq výrazy použijte gojq. U knihoven nespěchejte s instalací balíčků: jsonlines v Pythonu hlásí číslo a surový text každé vadné řádky a orjsonl čte a zapisuje rovnou komprimované soubory gzip/bzip2/zstd; balíček Node ndjson nevydal vydání od roku 2020 — vestavěné readline stačí.
4. Převod do a z JSON polí
Směr je zásadní; otočte ho a každý příkaz bude špatně. Zapamatujte si jedno pravidlo: pole → JSONL je map (každý záznam se zhušťuje na jeden řádek), JSONL → pole je collect (řádky se sbírají do seznamu).
# JSON pole → JSONL: -c zhušťuje každý prvek na jeden řádek
jq -c '.[]' records.json > records.jsonl
# JSONL → JSON pole: -s sesbírá celý vstup do jediného pole
jq -s '.' events.jsonl > records.json
# Filtrování JSONL po řádcích: -s není potřeba, jq zpracovává vstup jako stream
jq -c 'select(.active == true)' events.jsonl > active.jsonl
Pythonové jednořádkovky jsou ekvivalentní: pole → JSONL zapisuje json.dumps(r, ensure_ascii=False) + "\n" na záznam; opačný směr sbírá výsledky json.loads po řádcích do seznamu. Každý směr má jednu past. Při cestě tam (pole → JSONL) musí být každý skutečný konec řádku uvnitř záznamu escapovaný pomocí json.dumps — jinak se záznam roztrhne na více řádků. Při cestě zpět (JSONL → pole) nespojujte řádky do jedné obřího řetězce a neparsovejte ho — u milionů řádků je to zbytečná úplná kopie v paměti. Agregace (součty, seskupení, průměry) vyžadují -s, který načítá vše; u souborů o několika GB přejděte na awk nebo streamovaný akumulátor v Pythonu.
5. Šest druhů vadných řádků a jejich diagnostika
Téměř každá chyba JSONL žije na úrovni řádku. Validace po řádcích s hlášením čísel řádků je jediná spolehlivá diagnóza. Seřazeno podle četnosti:
| Symptom | Kořenová příčina | Oprava |
|---|---|---|
Extra data na konci řádku |
Přidané čárky mezi řádky | Odstraňte; každý řádek musí obstát sám za sebe |
| Jeden záznam rozdělený na více řádků | Neescapovaný skutečný konec řádku uvnitř záznamu (formátováno ručně) | Znovu zhušťte pomocí json.dumps / jq -c |
První řádek selže s Unexpected token ï |
Soubor má BOM | Uložte znovu jako UTF-8 bez BOM |
| Chyba jen na posledním řádku, i když vypadá správně | Chybějící závěrečný konec řádku nebo přebývající mezery | Přidejte \n; prázdné řádky přeskočte podle konvence |
| Chyba z konce řádku uvnitř řetězce | Doslovný konec řádku uvnitř řetězce, ne \n |
Opravte generátor; nikdy nespojujte JSON řetězce ručně |
| Velká celá čísla mění hodnotu | Parsování přes double (ořez na 2^53) | Použijte parser se zachováním přesnosti, viz přesnost bigint |
Diagnostický postup má vždy tři kroky: head -n 1 pro ověření kódování a struktury prvního řádku; spustit skript po řádcích, který hlásí číslo každé vadné řádky, místo aby se zastavil u první; potom vytáhnout vadné řádky po jedné — jeden řádek je jeden úplný záznam, jeho izolované zkoumání nestojí nic. Pro dávkové importy se doporučuje politika „karanténa, ne přerušení": vadné řádky s jejich čísly zapisujte do errors.jsonl, správné načítávejte dál a rozhodnutí o opakovaném importu vyvoďte z podílu vadných řádků.
6. JSONL přes HTTP: zpracování po řádcích a backpressure
Typický vzor NDJSON přes HTTP: server průběžně zapisuje, klient průběžně čte. Na klientské straně nevolejte response.json() — nabaluje celé tělo do paměti před parsováním, čímž postrádá smysl streamování. Čtěte stream těla přímo, rozdělte po \n a přeneste neúplný závěrečný fragment do další iterace:
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(); // poslední kus může být neúplný řádek
for (const line of lines) {
if (line.trim()) handle(JSON.parse(line));
}
}
Na straně serveru jsou dvě věci povinné: za prvé, když res.write() vrátí false, počkejte na událost drain (backpressure), jinak paměť roste neomezeně, kdykoli je konzument pomalejší než producent; za druhé, na streamovaných trasách vypněte middleware gzip — gzip musí před komprimací bufferovat celou odpověď, což zcela zbavuje NDJSON streamování smyslu. Obě chování jsou potvrzena lokálními testy a několika nezávislými anglickými zdroji, ne jsou dohady.
7. Kdy je JSONL špatná volba
Negativní prostor volby formátu je také důležitý. Tři scénáře by měly zůstat u JSON pole:
| Požadavek | Proč JSONL nesedí | Použijte místo toho |
|---|---|---|
| Validace struktury celého dokumentu | Žádná celková struktura; posun polí zůstane neodhalen | JSON pole + Schema |
| Náhodný přístup k záznamu N | Proměnlivé délky řádků; binární vyhledávání nelze | SQLite / Parquet |
| Hluboké zanořování, odkazy mezi záznamy | Každý řádek je izolovaný; odkazy nemají kde žít | Standardní JSON dokument |
Pravidlo palece v jedné větě: pokud záznamy přicházejí postupně nebo se spotřebovávají po řádcích (logy, události, exporty, trénovací sady), použijte JSONL; pokud malý objem vznikne jednou, spotřebuje se jednou a potřebuje sémantiku celého dokumentu, použijte JSON pole. Odvozené varianty (JSON Text Sequences s oddělovačem RS) jsou samostatné téma — viz článek o RFC 7464.
FAQ
Existuje skutečný rozdíl mezi JSONL a NDJSON?
Z inženýrského hlediska ne. Obojí znamená „jedna úplná JSON hodnota na řádek, žádný vnější obal, žádné čárky mezi řádky" a každý nástroj, který jeden podporuje, podporuje i druhý. Rozdíl je v pojmenování a zvycích ekosystému: .jsonl dominuje v datovém inženýrství a ML datasetech; .ndjson a application/x-ndjson dominují ve streamovaných HTTP API. Při integraci s externím systémem se řiďte příponou a media type uvedenými v jeho dokumentaci.
Můžu číst .jsonl přímo pomocí JSON.parse?
Ne celý soubor. JSON.parse přijímá přesně jeden JSON text, zatímco JSONL je mnoho samostatných hodnot uspořádaných do řádků, takže parsování celého souboru vždy selže s „Unexpected token" nebo „Extra data". Rozdělte na řádky a parsovejte každý neprázdný řádek zvlášť, s hlášením čísla řádku při selhání.
Pokud je jeden řádek v JSONL souboru poškozen, je celý soubor zkažený?
Ne — izolace chyb po řádcích je jádrem návrhu formátu. Vadný řádek postihuje jen sebe: přeskočte ho, zaznamenejte jeho číslo a čtěte dál. U dávkových importů dejte vadné řádky do karantény v samostatném souboru, správné záznamy načtěte obvyklým způsobem a rozhodnutí o opravě a opakovaném importu vyvoďte z podílu vadných řádků.
Jak převedu JSONL do Excelu nebo CSV?
Nejdřív ho převeďte na pole nebo vytáhněte pole po řádcích, poté spusťte tabulkovou konverzi: zhušťte pomocí jq -c '.[]' data.jsonl a předejte převodníku, nebo protahujte každý řádek přes json.loads do pandas. Pozor: zploštělá vnořená pole tvoří názvy sloupců s tečkami, pole typu array lze v buňce serializovat jen jako text a sloupce s dlouhými ID byste měli předem deklarovat jako řetězce, abyste nepřišli o přesnost.