JSON na CSV: odkud se bere hlavička a proč se mění hodnoty
Shrnutí: Devět experimentů na skutečném konvertoru: strategie „klíče z prvního záznamu" potichu ztrácí pole, null, absence i prázdný řetězec se stanou všichni prázdnými buňkami, 20místné celé číslo ztratí přesnost už v JSON.parse a tečka v klíči srazí dva různé cesty do jednoho sloupce. Jak se hlavička staví, které hodnoty se deformují a jak zvolit strategii podle toho, co s daty bude dále.

Když konverze JSON na CSV ztrácí data, skoro nikdy se to nestane během kroku „konverze". Ztráta vzniká ve dvou otázkách, které si nikdo nepoložil: kdo rozhodl o hlavičce a zůstává každá buňka rozpoznatelná jako ta hodnota, kterou byla?
Pole zmizí za třicet sekund
Mnoho konvertorů — včetně idiomatické pipeline jq — bere hlavičku pouze z prvního záznamu. Reprodukujte důsledky v Pythonu:
data = [{"id": 1, "name": "A"}, {"id": 2, "email": "b@x.com"}]
first_keys = list(data[0].keys()) # ['id', 'name']
rows = [[r.get(k, "") for k in first_keys] for r in data]
# E-mail druhého záznamu zmizí bez chyby i varování
Klíč email není v hlavičce: nemá ani právo stát se prázdnou buňkou — sloupec nikdy nevznikl a hodnota je potichu vyhozena. Oprava je sjednocení klíčů: projít všechny záznamy a vytvořit sloupec pro každý klíč, který se kdekoliv objeví. Tato samá data přes sjednocení dají hlavičku id,name,email, s prázdnou buňkou tam, kde chybí name — a nic se neztratí.
Přímý konvertor na naší stránce /convert/csv používá přesně tuto strategii. Pro vstup [{"id":1,"name":"A"},{"id":2,"email":"b@x.com"}] dává:
id,name,email
1,A,
2,,b@x.com
Klíče jsou seřazené podle prvního výskytu a chybějící pole se stanou prázdnými buňkami. jsonoperations i jsonbeam obě tomu říkají „oprava klasického bugu" — ale „používá sjednocení" je jen polovina příběhu. Sjednocení má svou vlastní cenu; to je další sekce.
Tři způsoby, jak postavit hlavičku — a komu která sedí
| Strategie hlavičky | Jak vzniká | Síla | Cena |
|---|---|---|---|
| Klíče z prvního záznamu | Čte se jen první záznam | Málo sloupců, stabilní, jeden řádek jq | Nová pole dalších záznamů se potichu ztrácejí |
| Sjednocení klíčů | Čtou se všechny záznamy | Žádné pole se neztratí | Řídké sloupce a mnoho prázdných, když se tvary liší |
| Explicitní seznam sloupců | Definovaný ručně nebo schématem | Plně ovladatelný, reprodukovatelný | Vyžaduje údržbu; nová pole vyžadují ruční aktualizaci |
Kritériem výběru není „které je sofistikovanější", ale co příjemce s tímto CSV udělá:
- Kolega, který se jen podívá: klíče z prvního záznamu stačí, tvary záznamů bývají obvykle jednotné.
- Report nebo datová pipeline: sjednocení klíčů je minimum — prázdná buňka je sama o sobě informace.
- Opakovaný automatický export: explicitní seznam sloupců. Drift tvaru (přidané pole nahoře, přejmenovaný klíč) musí spadnout hlasitě, ne potichu vyrábět tabulku, kterou si nikdo nevšimne, že je špatná.
Třetí možnost je nejvíce opomíjená. V automatizaci jsou „konverze proběhla úspěšně" a „konverze je správná" dvě různá tvrzení — a explicitní seznam sloupců je jediný mechanismus, který je oddělí.
Když klíč obsahuje tečku, cesty zploštění kolidují
Zploštění v tečkové notaci změní {"a": {"b": 2}} na sloupec a.b. Ale co když klíč sám obsahuje tečku? Otestováno v pandas:
import pandas as pd
d = [{"a.b": 1, "a": {"b": 2}}]
pd.json_normalize(d, sep=".")
# Přežije jen jeden sloupec: ['a.b'] — dva úplně různé klíče kolidují na téže cestě
Literální klíč a.b a vnořená cesta a → b produkují stejný název sloupce, jeden přepíše druhý a data se potichu poškodí. Je to sestrojený příklad, ale u skutečných dat se to děje: názvy metrik monitoringu (cpu.usage) a pole logů (error.type) milují tečky.
Dvě léčby:
- Změňte oddělovač.
sep="_"nebo vlastní oddělovač — poté, co ověříte, že původní klíče ho nikdy neobsahují. - Změňte notaci cest. JSON Pointer používá
/jako oddělovač (/a/b); Excel konvertor toolvore jde touto cestou. Má tutéž třídu dvojznačnosti — klíč s/se zapisuje jako~1— ale pravidla escapování jsou normalizovaná v RFC 6901, nevymýšená každým nástrojem zvlášť.
Cokoli si vyberete, zapište pravidlo do dokumentace exportu. Hlavička CSV je jediné „schéma", které tato data nesou; čtenář vaše pravidla zploštění nenajde nikde jinde.
Pole: tři strategie, každá ztrácí něco jiného
Pole jsou tou pravou překážkou na cestě JSON do CSV: buňka nemůže obsahovat více hodnot. Tři strategie se ztrátami ověřenými v praxi:
| Strategie | Tvar výstupu | Co se ztrácí | Ideální pro |
|---|---|---|---|
| JSON v buňce | Pole serializované jako JSON řetězec v buňce | Tabulkové nástroje nemohou prvky filtrovat ani agregovat | Věrnost napřed, ruční prohlížení |
| Indexované sloupce | tags.0, tags.1, sloupec pro každý |
Počet sloupců exploduje s nejdelším polem; mnoho prázdných | Malá pole pevné délky |
| Rozvinuté řádky | Řádek na prvek, pole rodiče zduplikovaná | Inflace řádků; chování prázdných polí je třeba určit zvlášť | Detailní analýza, agregace po prvku |
Naše konvertory /convert/csv a /convert/table používají JSON v buňce. Vstup [{"orderId":"A001","items":[{"sku":"X1","qty":2},{"sku":"Y9","qty":1}]}] dává:
orderId,items
A001,"[{""sku"":""X1"",""qty"":2},{""sku"":""Y9"",""qty"":1}]"
Když potřebujete rozvinuté řádky, pandas record_path to udělá přímo:
orders = [{"orderId": "A001", "total": 99,
"items": [{"sku": "X1", "qty": 2}, {"sku": "Y9", "qty": 1}]}]
items = pd.json_normalize(orders, record_path=["items"],
meta=["orderId", "total"], sep=".")
# Dva řádky detailů; orderId a total zduplikované v každém
U rozvinutí jsou tři pasti, které je třeba rozhodnout předem: rozvinout několik polí najednou znamená postavit kartézský součin; prázdná pole ve výchozím nastavení nevydají žádný řádek (váš seznam zákazníků ztratí lidi); a duplikovaná pole rodiče vyžadují deduplikaci, když dále agregujete po orderId. Průvodce convertcsvonline uvádí „nejdřív stanovte granularitu" jako první krok právě proto — jakmile je granularita fixní, všechny tři otázky mají jedinou odpověď.
Otestováno: hodnoty, které z konverze vycházejí změněné
Test věrnosti na našem skutečném konvertoru (Node v22.13.1 + papaparse, stejná implementace jako /convert/csv):
1. null, absence i prázdný řetězec se stanou stejnými prázdnými buňkami. Vstup [{"a":null},{"b":""},{"c":1}] — všechny tři buňky vycházejí prázdné. CSV nemá tři stavy, jen „znaky jsou" a „znaky nejsou". Po round-trip se všechny tři čtou jako ""; původní sémantika je nenávratně pryč.
2. Vnořené objekty a pole se stanou JSON řetězci. Uvozovky se podle RFC 4180 zdvojí ({""name"":""张三""}) a po vložení buňky do jakéhokoli JSON parseru se hodnota vrátí — proto JSON v buňce věrnost zachovává.
3. 20místné celé číslo ztratí přesnost už při parsování — a vina není na CSV. Vstup "id": 9007199254740993; v CSV už je 9007199254740992. JSON.parse v JavaScriptu používá IEEE 754 double: ztráta nastává při parsování, před jakýmkoli CSV konvertorem. Python json.load ji zachová celou. Pole typu identifikátor (snowflake ID, čísla objednávek) by měla od producenta přicházet jako řetězce. Více o přesnosti čísel v proč velká čísla ztrácejí přesnost.
4. Vodicí nuly v řetězcích jsou na textové vrstvě CSV v bezpečí. "phone": "00123" zůstane 00123 v CSV, protože je to řetězec od začátku do konce a nic ho poškrábe. Nuly poškrábe až pozdější krok, kdy Excel bere buňku jako číslo — viz čtyři způsoby, jak převést JSON na Excel.
5. Pole s čárkami, uvozovkami nebo odřádkováním. papaparse drží RFC 4180: pole do uvozovek, vnitřní uvozovky zdvojit. "He said \"hi\", ok" vychází jako "He said ""hi"", ok" a projde round-trip bez škrábnutí. Ručně psané serializátory, co obcházejí knihovnu, selžou skoro vždy právě tam.
Round-trip: co mění přepínač odvození typů
CSV → JSON musí u každé buňky uhodnout typ. Náš konvertor odvození typů ve výchozím stavu vypíná a vrací vše jako řetězce:
[{ "id": "9007199254740992", "phone": "00123" }]
Se zapnutým typeInference se id stane číslem (přesnost už je pryč), zatímco phone zůstane řetězcem — pravidla odvození vylučují tvary s vodicí nulou jako 010, které nejsou legálními číselnými literály, takže řetězec přežije spíš náhodou.
Zapamatujte si tuto asymetrii: vypnuté odvození vás v nejhorším případě stojí typy; zapnuté vás může stát data. Sloupce, které čísla potřebují, se převádějí explicitními definicemi sloupců, nikoli globálním odvozením. Implementace rowsToRecords z našeho dřívějšího článku JSON, CSV a tabulková data je přesně ten princip v kódu.
Vyberte strategii podle příjemce: rozhodovací tabulka
| Kdo CSV dostává | Strategie hlavičky | Zacházení s poli | Typy |
|---|---|---|---|
| Kolega, který ho otevře jednou | Sjednocení klíčů | JSON v buňce | Bez odvození |
| Excel / reporting | Sjednocení klíčů | JSON v buňce, nebo indexované sloupce při pevné délce | Sloupce-identifikátory vynuceně textem |
| Import do databáze | Explicitní seznam sloupců | Oddělené tabulky master/detail, nebo rozvinutí | Explicitní definice sloupců |
| BI / agregace | Explicitní seznam sloupců | Rozvinuté řádky, granularita = nejmenší jednotka analýzy | Explicitní definice sloupců |
| Automatická pipeline | Explicitní seznam sloupců | JSON v buňce, rozvinutí dále v proudu | Odvození vypnuto, konverze po sloupcích |
Společný zákon: čím blíž člověku, tím víc lze uvolnit; čím blíž stroji, tím víc musí být explicitního.
Checklist ke zkopírování
- [ ] Hlavička pochází z úplného sjednocení klíčů, nebo z explicitního seznamu? Pokud ne, umíte dokázat, že žádný záznam neztratil pole?
- [ ] Můžou klíče obsahovat tečky nebo lomítka? Byla ověřena kolize s oddělovačem zploštění?
- [ ] Je strategie polí zdokumentovaná? Je rozhodnuto chování prázdných polí?
- [ ] Pro
null, absenci a prázdný řetězec: co si z nich přečte příjemce? Zvolte pravidlo a zafixujte ho. - [ ] Cestují sloupce typu identifikátor (ID, čísla objednávek, telefony) jako řetězce od začátku do konce?
- [ ] Po rozvinutí je počet řádků roven součtu délek polí? Ověřeno výběrem?
- [ ] Příkladná pole s čárkami, uvozovkami a odřádkováním projdou round-trip bez poškození?
- [ ] Je globální odvození typů při reimportu vypnuté, s explicitními definicemi pro číselné sloupce?
Teprve když všech osm má jasnou odpověď, je ten CSV „správný" — a nejen „úspěšně zkonvertovaný".
FAQ
Proč konvertor nesplušťuje vnořené objekty do sloupců ve výchozím nastavení?
Zploštění vyvede počet sloupců z kontroly: hluboké vnoření produkuje názvy jako user.address.geo.lat a v kombinaci se sjednocením klíčů přidá jediný záznam jiného tvaru celou hromadu sloupců. JSON řetězce v buňkách zachovávají věrnost a stabilní počet sloupců; tavte explicitně, když potřebujete.
Co dělat, když hlavička obsahuje duplicitní názvy sloupců?
Kolize dvou různých klíčů po zploštění (jako a.b proti vnořenému a→b) se řeší nahoře: změňte oddělovač nebo přejděte na JSON Pointer. U duplicit uvnitř téhož objektu konvertory obvykle ponechají poslední hodnotu; duplicity v hlavičce závisí na implementaci — unparse papaparse nededuplikuje a Excel při otevření duplicity tiše sloučí nebo zahodí. Proto je to v checklistu.
Jsou duplikovaná pole rodiče po rozvinutí bug?
Ne — to je design. Rozvinutý řádek je řádek detailů a řádek detailů se musí dát po klíči zpřipojit k rodičovskému záznamu. Agregujte dále v proudu po klíči rodiče; pokud rozměr znepokojuje, exportujte soubor detailů a samostatnou master tabulku.
Jak ověřím, že moje konverze neztratila žádné pole?
Proveďte audit klíčů: spočítejte sjednocení všech klíčů zdrojového JSON, pak množinu hlaviček sloupců CSV a udělejte rozdíl. Neprázdný rozdíl znamená ztracená pole. V dávkových pipeline zakódujte to jako assert do testů: jakákoli výměna konvertoru, která začne ztrácet pole, automaticky spadne.