JSON zu CSV: Woher kommt der Spaltenkopf und warum ändern sich Werte
Fazit: Neun Experimente an einem echten Konverter: Die Strategie „Schlüssel der ersten Zeile" verliert Felder stillschweigend, null, fehlend und Leerstring werden alle zu leeren Zellen, eine 20-stellige Ganzzahl verliert ihre Präzision schon bei JSON.parse, und ein Punkt in einem Schlüssel lässt zwei verschiedene Pfade in derselben Spalte kollidieren. Wie der Spaltenkopf entsteht, welche Werte sich verändern und wie Sie eine Strategie nach dem nachgelagerten Zweck wählen.

Wenn eine JSON-zu-CSV-Konvertierung Daten verliert, passiert das fast nie im „Konvertieren"-Schritt selbst. Der Verlust geschieht in zwei Fragen, die niemand gestellt hat: Wer hat den Spaltenkopf entschieden, und bleibt jede Zelle als der Wert erkennbar, der sie war?
Ein Feld verschwindet in dreißig Sekunden
Viele Konverter — inklusive der idiomatischen jq-Pipeline — nehmen den Spaltenkopf nur aus dem ersten Datensatz. Reproduzieren Sie die Folgen in Python:
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]
# Die E-Mail des zweiten Datensatzes verschwindet ohne Fehler oder Warnung
Der Schlüssel email steht nicht im Kopf: Er hat nicht einmal das Recht, zu einer leeren Zelle zu werden — die Spalte wurde nie erzeugt, und der Wert wird stillschweigend weggeworfen. Die Korrektur ist die Schlüsselvereinigung: alle Datensätze durchlaufen und für jeden irgendwo auftauchenden Schlüssel eine Spalte anlegen. Dieselben Daten durch die Vereinigung ergeben den Kopf id,name,email, mit einer leeren Zelle, wo name fehlt — und nichts geht verloren.
Der Direktkonverter unserer Seite /convert/csv wendet genau diese Strategie an. Bei der Eingabe [{"id":1,"name":"A"},{"id":2,"email":"b@x.com"}] erzeugt er:
id,name,email
1,A,
2,,b@x.com
Die Schlüssel werden nach erstem Auftreten sortiert, fehlende Felder werden zu leeren Zellen. jsonoperations und jsonbeam nennen beide genau das „die Korrektur des klassischen Fehlers" — aber „nutzt die Vereinigung" ist nur die halbe Geschichte. Die Vereinigung hat ihren eigenen Preis; das ist der nächste Abschnitt.
Drei Arten, einen Spaltenkopf zu bauen — und wem welche passt
| Kopffstrategie | Wie sie entsteht | Stärke | Preis |
|---|---|---|---|
| Schlüssel der ersten Zeile | Nur der erste Datensatz wird gelesen | Wenige Spalten, stabil, eine jq-Zeile | Neue Felder späterer Datensätze gehen still verloren |
| Schlüsselvereinigung | Alle Datensätze werden gelesen | Kein Feld geht verloren | Spärliche Spalten und viele Leere bei unterschiedlichen Formen |
| Explizite Spaltenliste | Manuell oder per Schema definiert | Voll kontrollierbar, reproduzierbar | Muss gepflegt werden; neue Felder erfordern manuelle Aktualisierung |
Das Auswahlkriterium ist nicht „welches ist raffinierter", sondern was das downstream mit dieser CSV macht:
- Ein Kollege, der kurz hineinschaut: Schlüssel der ersten Zeile genügen, Datensatzformen sind meist einheitlich.
- Ein Report oder eine Datenpipeline: Die Schlüsselvereinigung ist das Minimum — eine leere Zelle ist selbst Information.
- Ein wiederkehrender automatischer Export: die explizite Spaltenliste. Formdrift (Feld hinzugekommen, Schlüssel umbenannt) muss laut scheitern, nicht stillschweigend eine Tabelle produzieren, die niemand als falsch bemerkt.
Die dritte Option ist die am meisten vernachlässigte. In der Automatisierung sind „die Konvertierung war erfolgreich" und „die Konvertierung ist korrekt" zwei verschiedene Aussagen — und die explizite Spaltenliste ist der einzige Mechanismus, der sie trennt.
Wenn ein Schlüssel einen Punkt enthält, kollidieren Flattening-Pfade
Flattening in Punktnotation macht aus {"a": {"b": 2}} die Spalte a.b. Aber was, wenn der Schlüssel selbst einen Punkt enthält? Mit pandas getestet:
import pandas as pd
d = [{"a.b": 1, "a": {"b": 2}}]
pd.json_normalize(d, sep=".")
# Nur eine Spalte überlebt: ['a.b'] — zwei völlig verschiedene Schlüssel kollidieren auf demselben Pfad
Der literale Schlüssel a.b und der verschachtelte Pfad a → b erzeugen denselben Spaltennamen, einer überschreibt den anderen, und die Daten werden stillschweigend korrumpiert. Das ist ein konstruiertes Beispiel, aber es passiert mit echten Daten: Monitoring-Metriknamen (cpu.usage) und Logfelder (error.type) lieben Punkte.
Zwei Gegenmittel:
- Das Trennzeichen wechseln.
sep="_"oder ein eigenes Trennzeichen — nachdem Sie geprüft haben, dass Originalschlüssel es nie enthalten. - Die Pfadnotation wechseln. JSON Pointer nutzt
/als Trennzeichen (/a/b); der Excel-Konverter von toolvore geht diesen Weg. Er hat dieselbe Ambiguitätsklasse — ein Schlüssel mit/muss als~1geschrieben werden — aber die Escape-Regeln sind in RFC 6901 normiert statt von jedem Tool erfunden.
Welche Wahl Sie treffen: Schreiben Sie die Regel in die Export-Dokumentation. Der CSV-Kopf ist das einzige „Schema", das diese Daten tragen; der Leser findet Ihre Flattening-Regeln nirgendwo sonst.
Arrays: drei Strategien, jede verliert etwas anderes
Arrays sind das eigentliche Hindernis bei JSON zu CSV: Eine Zelle kann nicht mehrere Werte enthalten. Drei Strategien mit praktisch verifizierten Verlusten:
| Strategie | Ausgabeform | Was verloren geht | Ideal für |
|---|---|---|---|
| JSON-in-Zelle | Das Array als JSON-String serialisiert in der Zelle | Tabellenwerkzeuge können Elemente weder filtern noch aggregieren | Treue zuerst, manuelle Inspektion |
| Indexierte Spalten | tags.0, tags.1, je eine Spalte |
Spaltenzahl explodiert mit dem längsten Array; viele Leere | Kleine Arrays fester Länge |
| Explodierte Zeilen | Eine Zeile pro Element, Elternfelder dupliziert | Zeileninflation; Verhalten leerer Arrays muss separat festgelegt werden | Detailanalyse, Aggregation pro Element |
Unsere /convert/csv- und /convert/table-Konverter nutzen JSON-in-Zelle. Die Eingabe [{"orderId":"A001","items":[{"sku":"X1","qty":2},{"sku":"Y9","qty":1}]}] ergibt:
orderId,items
A001,"[{""sku"":""X1"",""qty"":2},{""sku"":""Y9"",""qty"":1}]"
Wenn Sie explodierte Zeilen brauchen, macht pandas record_path das direkt:
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=".")
# Zwei Detailzeilen; orderId und total auf jeder dupliziert
Beim Explodieren gibt es drei Fallen, die Sie vorher entscheiden: Mehrere Arrays gleichzeitig zu explodieren baut ein kartesisches Produkt; leere Arrays erzeugen standardmäßig keine Zeile (Ihre Kundenliste verliert Personen); und duplizierte Elternfelder erfordern Deduplizierung, wenn Sie downstream nach orderId aggregieren. Der convertcsvonline-Guide nennt „zuerst die Granularität festlegen" aus genau diesem Grund als ersten Schritt — ist die Granularität fixiert, haben alle drei Fragen je eine Antwort.
Getestet: Werte, die verändert aus der Konvertierung kommen
Ein Treuetest an unserem echten Konverter (Node v22.13.1 + papaparse, dieselbe Implementierung wie /convert/csv):
1. null, Fehlen und Leerstring werden alle zu leeren Zellen. Eingabe [{"a":null},{"b":""},{"c":1}] — alle drei Zellen kommen leer heraus. CSV hat keine drei Zustände, nur „Zeichen vorhanden" und „Zeichen fehlen". Nach einem Roundtrip lesen sich alle drei als ""; die ursprüngliche Semantik ist unwiederbringlich weg.
2. Verschachtelte Objekte und Arrays werden zu JSON-Strings. Anführungszeichen werden nach RFC 4180 verdoppelt ({""name"":""张三""}), und sobald Sie die Zelle in einen beliebigen JSON-Parser stecken, ist der Wert wieder da — deshalb ist JSON-in-Zelle treu.
3. Eine 20-stellige Ganzzahl verliert ihre Präzision schon beim Parsen — und die Schuld liegt nicht beim CSV. Eingabe "id": 9007199254740993; in der CSV steht bereits 9007199254740992. JSON.parse in JavaScript nutzt IEEE-754-Doubles: Der Verlust passiert beim Parsing, vor jedem CSV-Konverter. Python json.load bewahrt sie vollständig. ID-artige Felder (Snowflake-IDs, Bestellnummern) sollten vom Produzenten schon als Strings ankommen. Mehr zu Zahlenpräzision in warum große Zahlen ihre Präzision verlieren.
4. Führende Nullen in Strings sind auf der CSV-Textebene sicher. "phone": "00123" bleibt als 00123 in der CSV, denn es ist von Anfang bis Ende ein String, und nichts kratzt daran. Kratzen tun die Nullen erst der spätere Schritt, in dem Excel die Zelle als Zahl behandelt — siehe vier Wege, JSON nach Excel zu konvertieren.
5. Felder mit Kommas, Anführungszeichen oder Zeilenumbrüchen. papaparse folgt RFC 4180: Feld zitieren, innere Anführungszeichen verdoppeln. "He said \"hi\", ok" kommt als "He said ""hi"", ok" heraus und übersteht den Roundtrip intakt. Handgeschriebene Serialisierer, die eine Bibliothek umgehen, scheitern fast immer genau dort.
Roundtrip: was der Inferenz-Schalter verändert
CSV → JSON muss für jede Zelle einen Typ raten. Unser Konverter schaltet die Typinferenz standardmäßig ab und gibt alles als String zurück:
[{ "id": "9007199254740992", "phone": "00123" }]
Mit aktivierter typeInference wird id zur Zahl (die Präzision ist bereits weg), während phone String bleibt — die Inferenzregeln schließen führend-Null-Formen wie 010 aus, die keine legalen Zahlenliterale sind, sodass der String nur durch Glück überlebt.
Prägen Sie sich diese Asymmetrie ein: Inferenz ab kostet Sie im schlimmsten Fall Typen; Inferenz an kann Sie Daten kosten. Spalten, die Zahlen brauchen, werden über explizite Spaltendefinitionen konvertiert, nicht einer globalen Inferenz anvertraut. Die rowsToRecords-Implementierung aus unserem früheren Artikel JSON, CSV und tabellarische Daten ist genau dieses Prinzip als Code.
Strategie nach dem nachgelagerten Ziel: eine Entscheidungstabelle
| Wer die CSV bekommt | Kopffstrategie | Array-Behandlung | Typen |
|---|---|---|---|
| Ein Kollege, der sie einmal öffnet | Schlüsselvereinigung | JSON-in-Zelle | Ohne Inferenz |
| Excel / Reporting | Schlüsselvereinigung | JSON-in-Zelle, oder indexierte Spalten bei fester Länge | ID-Spalten erzwungen als Text |
| Datenbankimport | Explizite Spaltenliste | Getrennte Master-/Detailtabellen oder Explodieren | Explizite Spaltendefinitionen |
| BI / Aggregation | Explizite Spaltenliste | Explodierte Zeilen, Granularität = kleinste Analyseeinheit | Explizite Spaltendefinitionen |
| Automatisierte Pipeline | Explizite Spaltenliste | JSON-in-Zelle, Explodieren downstream | Inferenz aus, Spalte für Spalte konvertieren |
Das gemeinsame Gesetz: Je näher am Menschen, desto eher darf man sich erlauben zu lockern; je näher an der Maschine, desto mehr muss alles explizit sein.
Eine Checkliste zum Kopieren
- [ ] Kommt der Kopf aus einer vollständigen Schlüsselvereinigung oder aus einer expliziten Liste? Falls nicht: Können Sie beweisen, dass kein Datensatz ein Feld verloren hat?
- [ ] Können Schlüssel Punkte oder Schrägstriche enthalten? Wurde die Kollision mit dem Flattening-Trennzeichen geprüft?
- [ ] Ist die Array-Strategie dokumentiert? Ist das Verhalten leerer Arrays entschieden?
- [ ] Für
null, Fehlen und Leerstring: Was liest der downstream je davon? Wählen Sie eine Regel und frieren Sie sie ein. - [ ] Reisen ID-artige Spalten (IDs, Bestellnummern, Telefone) als Strings von Ende zu Ende?
- [ ] Entspricht die Zeilenzahl nach dem Explodieren der Summe der Arraylängen? Stichprobenartig geprüft?
- [ ] Überstehen Beispielfelder mit Kommas, Anführungszeichen und Zeilenumbrüchen einen Roundtrip unversehrt?
- [ ] Ist die globale Typinferenz beim Reimport ausgeschaltet, mit expliziten Definitionen für numerische Spalten?
Erst wenn alle acht eine klare Antwort haben, ist diese CSV „korrekt" — und nicht bloß „erfolgreich konvertiert".
FAQ
Warum flacht der Konverter verschachtelte Objekte nicht standardmäßig in Spalten ab?
Flattening lässt die Spaltenzahl aus dem Ruder laufen: tiefe Verschachtelung erzeugt Namen wie user.address.geo.lat, und kombiniert mit der Schlüsselvereinigung fügt ein einziger Datensatz anderer Form einen ganzen Stapel Spalten hinzu. JSON-Strings in Zellen bewahren Treue und eine stabile Spaltenzahl; flattens Sie explizit, wenn Sie es brauchen.
Was tun, wenn der Kopf doppelte Spaltennamen enthält?
Die Kollision zweier verschiedener Schlüssel nach dem Flattening (wie a.b gegen das verschachtelte a→b) muss upstream gelöst werden: Trennzeichen wechseln oder auf JSON Pointer umsteigen. Für Duplikate innerhalb desselben Objekts behalten Konverter meist den letzten Wert; Duplikate im Kopf sind implementierungsabhängig — papaparse-unparse dedupliziert nicht, und Excel schmilzt oder wirft Duplikate beim Öffnen still weg. Genau deshalb steht es in der Checkliste.
Sind duplizierte Elternfelder nach dem Explodieren ein Bug?
Nein — das ist Design. Eine explodierte Zeile ist eine Detailzeile, und eine Detailzeile muss per Schlüssel an ihren Eltern-Datensatz rückankoppelbar sein. Aggregieren Sie downstream über den Elternschlüssel; wenn die Größe Sorge macht, exportieren Sie die Detaildatei und eine separate Mastertabelle.
Wie prüfe ich, dass meine Konvertierung kein Feld verloren hat?
Führen Sie ein Schlüssel-Audit durch: Berechnen Sie die Vereinigung aller Schlüssel des Quell-JSON, dann die Menge der Spaltenköpfe der CSV, und bilden Sie die Differenz. Eine nicht-leere Differenz bedeutet verlorene Felder. In Batch-Pipelines kodieren Sie das als Assertion in Ihre Tests: Jeder Konverterwechsel, der beginnt, Felder zu verlieren, schlägt automatisch fehl.