JSON Toolkit

JSON in CSV und tabellarische Daten konvertieren – und zurück

Fazit: CSV ist zweidimensional, JSON baumförmig. Das Schwierige an der Konvertierung ist nicht der Code, sondern vorab zu entscheiden, wie Arrays abgeflacht und wie Typen erhalten werden.

Einsteiger~10 Min. LesezeitVeröffentlicht 2026-10-30Aktualisiert 2026-10-30
Titelbild für die Konvertierung JSON ↔ CSV und tabellarischer Daten

Kurz gesagt: Flachen Sie mit Punkt-Schlüsseln und JSON-stringifizierten Arrays ab, wenn Menschen den Export lesen; teilen Sie in eine Elterntabelle plus verknüpfte Tabellen, wenn eine Datenbank geladen wird; erweitern Sie Arrays nur für Pivots zu zusätzlichen Zeilen; beim Zurückkonvertieren nach ausdrücklichen Spaltendefinitionen statt globaler Inferenz umwandeln.

Einen Baum in eine Tabelle abzuflachen verliert stets die Hierarchie; eine Tabelle in einen Baum zurückzuführen rät stets die Typen. Beide Richtungen brauchen vorab festgelegte Regeln.

1. Was CSV tatsächlich spezifiziert

RFC 4180 umfasst nur wenige Seiten und legt weniger fest, als viele annehmen:

  1. Jeder Datensatz nimmt eine Zeile ein, die Felder werden durch Kommas getrennt;
  2. Der Zeilenabschluss ist CRLF, in der Praxis wird aber auch \n akzeptiert;
  3. Ein Feld mit Komma, doppeltem Anführungszeichen oder Zeilenumbruch muss in doppelte Anführungszeichen gefasst werden;
  4. Doppelte Anführungszeichen innerhalb eines Feldes werden als zwei Anführungszeichen "" geschrieben;
  5. Die erste Zeile darf ein Header sein;
  6. Es wird keine Zeichenkodierung festgelegt, und ebenso keine Typen.
id,name,remark
1,张三,"喜欢 JSON,讨厌尾逗号"
2,李四,"他说:""这行有引号"""
3,王五,"多行
备注"

Beachten Sie Regel 3: Felder mit Zeilenumbrüchen sind zulässig, ein naiver Parser, der nach \n und dann nach , trennt, ist also garantiert falsch.

2. JSON → CSV: zuerst die Struktur entscheiden

Es gibt nur drei Strategien, eine Baumstruktur in eine zweidimensionale Tabelle abzuflachen:

Strategie 1: Felder abflachen (am verbreitetsten)

{ "id": 1, "user": { "name": "张三", "city": "杭州" }, "tags": ["a", "b"] }
id,user.name,user.city,tags
1,张三,杭州,"[""a"",""b""]"
  • Verschachtelte Objektschlüssel mit . verbinden;
  • Arrays lassen sich nicht als Spalten ausdrücken, also JSON.stringify sie direkt in die Zelle (was weiterhin ein zulässiges CSV-Feld ist);
  • Nachteil: Tabellenwerkzeuge können das Array nicht filtern, ein zweites Parsen ist also nötig.

Strategie 2: das Array in mehrere Zeilen erweitern

id,user.name,tag
1,张三,a
1,张三,b

Das passt zu Eins-zu-viele-Beziehungen, bei denen nachgelagerte Konsumenten pro Element aggregieren. Der Nachteil sind duplizierte Elternfelder und eine anschwellende Zeilenzahl.

Strategie 3: in mehrere Tabellen aufteilen

Eine Haupttabelle plus verknüpfte Tabellen über eine id – der sauberste Ansatz, aber er geht über „ein einzelner Export“ hinaus und ähnelt eher Datenmodellierung.

Welche? Strategie 1 für Exporte für Menschen, Strategie 3 für das Laden in eine Datenbank, Strategie 2 für das Pivotieren von Daten.

3. CSV → JSON: wie Typen geraten werden

CSV hat keine Typen, jeder Schritt der Wiederherstellung ist also ein Raten:

Zellinhalt Mögliche Bedeutung Risiko direkter Zahlenumwandlung
00123 Personalnummer, Postleitzahl Führende Nullen verloren → 123
1.10 Versionsnummer, Betrag Wird 1.1
1e5 Kennung oder wissenschaftliche Notation Wird 100000
2026-10-30 Datum oder String Mehrdeutigkeit bei Zeitzone und Format
13800138000 Telefonnummer Bei 11 Ziffern noch sicher, manche IDs verlieren aber Präzision
"" Leerer String oder null Semantische Mehrdeutigkeit

Vorgeschlagener Standard: alles als String behandeln und ein Schema oder eine ausdrückliche Spaltendefinition entscheiden lassen, welche Spalten Zahlen oder Booleans werden. „Intelligente Inferenz“ ist beim Export erfreulich und beim Import eine Quelle von Vorfällen.

type ColumnType = 'string' | 'number' | 'boolean' | 'json';

/**
 * Converts CSV rows into objects according to the column definitions.
 * @param rows The raw row array; the first row is the header
 * @param columnTypes Mapping from column name to type
 * @returns The converted array of objects
 */
function rowsToRecords(
  rows: string[][],
  columnTypes: Record<string, ColumnType>,
): Array<Record<string, unknown>> {
  const [headerRow, ...dataRows] = rows;

  return dataRows.map((dataRow) => {
    const record: Record<string, unknown> = {};

    headerRow.forEach((columnName, columnIndex) => {
      const rawValue = dataRow[columnIndex] ?? '';
      const columnType = columnTypes[columnName] ?? 'string';

      if (columnType === 'number') {
        const numericValue = Number(rawValue);
        record[columnName] = Number.isFinite(numericValue) ? numericValue : null;
        return;
      }
      if (columnType === 'boolean') {
        record[columnName] = rawValue === 'true' || rawValue === '1';
        return;
      }
      if (columnType === 'json') {
        try {
          record[columnName] = JSON.parse(rawValue);
        } catch {
          record[columnName] = rawValue;
        }
        return;
      }
      record[columnName] = rawValue;
    });

    return record;
  });
}

4. Drei Fallstricke rund um Excel

4.1 Kodierung

Wenn Excel eine UTF-8-CSV ohne BOM öffnet, wird chinesischer Text zu Mojibake. Ein BOM (\uFEFF) beim Export hinzuzufügen ist die günstigste Kompatibilitätsmaßnahme:

const bom = '\uFEFF';
const csvContent = `${bom}${headerLine}\n${bodyLines.join('\n')}`;

4.2 Lange Zahlen werden zu wissenschaftlicher Notation

Eine 18-stellige Personalausweisnummer oder eine 19-stellige Bestellnummer erscheint in Excel als 1.23457E+17, und der Wert selbst ist bereits abgeschnitten. Ein vorangestellter Tabulator oder das Verpacken als Formel (="1234...") bewahrt Textformatierung, verwandelt die Zelle aber in eine Formel. Der sicherere Weg ist, Nutzer auf „die Importfunktion verwenden und die Spalte als Text angeben“ hinzuweisen oder neben dem Export eine .xlsx anzubieten.

4.3 Trennzeichen werden von der Locale beeinflusst

Manche europäische Locales verwenden standardmäßig das Semikolon ; als Trennzeichen. Beim Verteilen über Regionen hinweg entweder CSV auf Kommas festlegen und das sagen oder eine TSV-Version (tab-getrennt) anbieten – TSV ist toleranter gegenüber Locales.

5. Umgang mit großen Dateien

CSVs mit mehr als hunderttausend Zeilen nicht auf einmal in den Speicher laden:

import { createReadStream } from 'node:fs';
import { parse } from 'csv-parse';

const parser = createReadStream('orders.csv').pipe(
  parse({ columns: true, skipEmptyLines: true, bom: true }),
);

for await (const record of parser) {
  // Process one record at a time; memory usage is independent of file size
}

Die Exportrichtung funktioniert genauso: fortlaufend generieren und in einen Stream schreiben, statt einen riesigen String zu verketten.

6. Selbstprüfung der Roundtrip-Konsistenz

Ein CSV-Roundtrip (JSON → CSV → JSON) ist fast nie exakt identisch, aber Sie können festlegen, welche Verluste akzeptabel sind:

Prüfung Erwartung
Feldanzahl Identisch
String-Werte Identisch (einschließlich führender Nullen)
Numerische Typen Durch die Spaltendefinition bestimmt, nicht durch Inferenz
Verschachtelte Objekte Über Punktpfade wiederherstellbar
Arrays Nach Serialisierung zu einem JSON-String sichtbar
Leere Werte Auf null oder den leeren String normalisiert – genau eines wählen

Diese Tabelle in Tests zu überführen ist weit wirksamer als eine mündliche Absprache.

7. Checkliste

  • [ ] Die Semantik „eine Zeile ist ein Datensatz“ ist ausdrücklich benannt und spiegelt sich im Exportdateinamen wider;
  • [ ] Jede Spalte, die Präzision verlieren kann, wird als String exportiert;
  • [ ] Chinesische CSVs enthalten ein BOM, oder die Kodierung ist ausdrücklich angegeben;
  • [ ] Felder mit Kommas, Anführungszeichen oder Zeilenumbrüchen werden korrekt escaped (Bibliothek verwenden, keine String-Verkettung von Hand);
  • [ ] Große Dateien laufen über Streaming-Lesen und -Schreiben;
  • [ ] Importe wandeln Typen nach Spaltendefinition um, nie über globale intelligente Inferenz.

FAQ

Wie wähle ich zwischen Abflachen, Zeilenerweiterung und Tabellenaufteilung?

Flachen Sie für Exporte für Menschen ab: verschachtelte Schlüssel mit . verbinden und Arrays per JSON.stringify in die Zelle legen, zum Preis, dass sich diese Arrays in einer Tabellenkalkulation nicht filtern lassen. Teilen Sie in eine Elterntabelle plus verknüpfte Tabellen, wenn das Ziel eine Datenbank ist. Erweitern Sie Arrays zu zusätzlichen Zeilen, wenn nachgelagerte Konsumenten pro Element aggregieren, und akzeptieren Sie duplizierte Elternfelder.

Warum sollte ein CSV-Import Typen nicht automatisch inferieren?

RFC 4180 legt Trennzeichen und Escaping fest, aber keine Typen, jeder Wiederherstellungsschritt ist also ein Raten: 00123 verliert seine führenden Nullen, 1.10 wird 1.1, und 1e5 wird 100000. Behandeln Sie standardmäßig alles als String und lassen Sie ein Schema oder eine ausdrückliche Spaltendefinition entscheiden, welche Spalten Zahlen oder Booleans werden.

Sollte ich erwarten, dass ein CSV-Roundtrip das ursprüngliche JSON exakt reproduziert?

Fast nie. Legen Sie stattdessen die akzeptablen Verluste fest: identische Feldanzahl, identische String-Werte einschließlich führender Nullen, numerische Typen durch die Spaltendefinition bestimmt, verschachtelte Objekte über Punktpfade wiederherstellbar, Arrays nach Serialisierung sichtbar und leere Werte auf null oder den leeren String normalisiert – genau eines wählen.