Was beim Umwandeln zwischen JSON, YAML, CSV und XML verloren geht

Wie sich JSON, YAML, CSV, XML und TOML bei Kommentaren, Typen, Verschachtelung, Datumsangaben und Schlüsselreihenfolge unterscheiden und welche dieser Einzelheiten jede Umwandlung zwischen ihnen still und leise wegwirft.

Jede Umwandlung zwischen Datenformaten wirft weg, was die Quelle ausdrücken konnte und das Ziel nicht. Manche Verluste sind offensichtlich: Kommentare verschwinden in dem Moment, in dem YAML zu JSON wird. Die meisten sind leise. Ein Datum kommt als Zeichenkette an, ein Array wird zu fünf Spalten, eine ID, die auf 3 endete, endet jetzt auf 2. Die nützliche Gewohnheit ist zu wissen, welche Einzelheit verschwinden wird, bevor sie verschwindet.

Was jedes Format tragen kann

KommentareTypenVerschachtelungDatumsangabenSchlüsselreihenfolgeDoppelte Schlüssel
JSONNeinstring, number, boolean, null, object, arrayJaKein eigener TypLaut Spezifikation ungeordnet, von den meisten Parsern erhaltenUndefiniert, meist gewinnt der letzte
YAMLJa, #Die von JSON, dazu Zeitstempel und mehr in 1.1JaJa in 1.1, in 1.2 vom Tag abhängigIn der Datei erhalten, nach dem Parsen nichtUngültig, wird aber oft akzeptiert
CSVNeinKeine, jede Zelle ist TextNein, nur flache ZeilenNeinDurch die Kopfzeile festgelegtZulässig und mehrdeutig
XMLJa, <!-- -->Ohne Schema keine, mit XSD typisiertJaNur über ein SchemaElemente geordnet, Attribute nichtWiederholte Elemente normal, doppelte Attribute ein Fehler
TOMLJa, #string, integer, float, boolean, date, time, date-time, array, tableJa, über TabellenJa, vollwertigNicht bedeutsamImmer ein Fehler

Zwei Spalten sorgen für die meisten Überraschungen. Typen entscheidet, ob 007 überlebt oder als Zahl 7 ankommt, und Verschachtelung entscheidet, ob eine Umwandlung nach CSV ohne eine erfundene Konvention möglich ist.

CSV ist flach, und jede Zelle ist Text

CSV hat eine einzige Form: Zeilen aus Feldern. Es gibt keine Möglichkeit, ein Objekt in einer Zelle auszudrücken, ein Konverter muss sich also etwas ausdenken. Üblicherweise verflacht er mit Punktpfaden, aus diesem hier:

[{ "id": 1, "name": { "first": "Ada" }, "tags": ["admin", "ops"] }]

wird also:

id,name.first,tags.0,tags.1
1,Ada,admin,ops

Das lässt sich sauber wieder auffalten, bis zwei Dinge passieren. Arrays unterschiedlicher Länge verbreitern die Kopfzeile bis zum längsten Datensatz, eine Zeile mit drei Tags fügt also eine Spalte tags.2 hinzu und lässt in jeder anderen Zeile eine leere Zelle. Und ein Schlüssel, der bereits einen Punkt enthält, ist danach nicht mehr von einem verschachtelten Pfad zu unterscheiden, der Rückweg baut also die falsche Struktur auf. Die Alternative ist eine JSON-Zeichenkette in der Zelle, "[""admin"",""ops""]" mit verdoppelten Anführungszeichen, wie CSV es verlangt, die nichts verliert und die nachgelagert niemand versteht.

Typen sind die zweite Hälfte. Eine Zelle enthält Zeichen, ein Leser muss also raten. Raten macht aus 007 die 7, aus 1.0 und 1 einen Wert, aus einer Postleitzahl eine ohne führende Null und aus einem Firmencode TRUE einen Wahrheitswert. Nicht zu raten macht aus jeder Zahl eine Zeichenkette. Die Typerkennung in CSV to JSON oder in TSV to JSON ist eine Vermutung mit vernünftigen Voreinstellungen und keine Wiederherstellung, denn die ursprünglichen Typen wurden nie aufgeschrieben.

Eine leere Zelle kann leere Zeichenkette, null, Null oder nicht zutreffend bedeuten; entscheide, was sie bedeutet, und sag es dazu, denn die Datei kann es nicht. Trennzeichen zu tauschen, wie CSV to TSV es tut, ist hier die eine sichere Umwandlung: Es gibt keine Typen, die Schaden nehmen könnten.

YAML rät den Typ für dich

YAML leitet Typen aus Text ohne Anführungszeichen ab, was es angenehm zu schreiben und riskant umzuwandeln macht.

country: no
version: 1.20
build: 010
start: 12:30

Unter einem YAML-1.1-Parser, wie ihn PyYAML und viele ältere Bibliotheken bis heute implementieren, ist das:

{ "country": false, "version": 1.2, "build": 8, "start": 750 }

Alle vier haben ihre Bedeutung geändert. no ist das Norwegen-Problem: YAML 1.1 behandelt y, yes, no, on und off als Wahrheitswerte, der Ländercode wird also zu false. 1.20 ist eine Gleitkommazahl, die abschließende Null ist also weg. 010 passt auf das Oktalmuster und wird zu 8. 12:30 passt auf das Sexagesimalmuster und wird zu 750, einer Anzahl Minuten.

Ein Parser mit dem YAML-1.2-Kernschema wie das aktuelle js-yaml liest dieselbe Datei als die Zeichenkette "no", 010 als 10 und "12:30" als Zeichenkette. Eine Datei, zwei Parser, unterschiedliche Daten. Die Spezifikationen widersprechen sich tatsächlich; 1.2 hat die Regeln für Sexagesimalzahlen und Wahrheitswörter bewusst gestrichen.

Die Verteidigung ist ein einziges Zeichen. Setze alles in Anführungszeichen, was Text sein soll: country: "no", version: "1.20". Der Konverter zwischen YAML und JSON zeigt, was ein Parser aus einer Datei gemacht hat, denn JSON hat keinen Ort, an dem sich die Mehrdeutigkeit verstecken könnte.

JSON hat keine Datumsangaben, keine Kommentare und eine Grenze bei 53 Bit

JSON-Zahlen sind IEEE-754-Doubles, ganze Zahlen bleiben also nur bis 2^53 exakt, das sind 9007199254740992. Darüber hinaus:

{ "id": 9007199254740993 }

Parse das in den meisten Sprachen und serialisiere es erneut, und es kommt als 9007199254740992 zurück. Der Wert lässt sich überhaupt nicht darstellen, er ist also weniger gerundet als schlicht nicht verfügbar. Deshalb senden Discord, Twitter und andere Snowflake-APIs IDs als Zeichenketten: Eine 64-Bit-Ganzzahl passt nicht in eine JSON-Zahl.

Einen Datumstyp gibt es ebenfalls nicht, Datumsangaben reisen also als Zeichenketten, konventionell nach ISO 8601, und nichts kennzeichnet "2026-08-25" als Datum statt als Text. Über TOML oder einen YAML-1.1-Parser kann daraus ein echtes Datum werden, das dann anders formatiert zurückkommt. JSON hat außerdem keine Kommentare und keine abschließenden Kommas, Anmerkungen in einer YAML- oder TOML-Quelle sind also verschwunden und nicht verschoben. TypeScript-Interfaces aus einer Stichprobe zu erzeugen erbt beide blinden Flecken: Eine Zeichenkette ist kein Datum, und ein fehlendes Feld ist nicht optional.

XML passt nicht in JSON, in keiner Richtung

XML unterscheidet Attribute von Kindelementen, und JSON hat nur Schlüssel.

<user id="7" active="true">
  <name>Ada</name>
  <tag>admin</tag>
  <tag>ops</tag>
</user>

Ein Konverter wählt eine Konvention, meist ein @ vor Attributen und das Zusammenfassen wiederholter Elemente zu einem Array. Das ist auf eine bestimmte Weise verlustbehaftet: Bei einem einzelnen <tag> gibt es kein Array, die Form der Ausgabe hängt also von den Daten ab statt vom Schema, und Code, der eine Liste erwartet, bricht bei dem Datensatz mit genau einem Tag. Gemischter Inhalt wie <p>Hello <b>there</b> friend</p> hat keine Entsprechung in JSON, und Namensräume, CDATA und Kommentare verschwinden.

Die Gegenrichtung verliert anderes. Elementnamen dürfen nicht mit einer Ziffer beginnen und keine Leerzeichen enthalten, beliebige JSON-Schlüssel müssen also verstümmelt werden. null wird per Konvention zu einem leeren Element oder zu xsi:nil. Ein Array auf oberster Ebene braucht ein umschließendes Element, das nie in der Quelle stand. Und nichts in JSON hält fest, ob ein Wert einmal ein Attribut war, JSON to XML gibt also nur Elemente aus. CSV to XML trifft diese Entscheidung offen und fragt nach Elementen oder Attributen.

Schlüsselreihenfolge und doppelte Schlüssel

Die Schlüsselreihenfolge ist nirgends garantiert und fast überall erhalten. Die JSON-Spezifikation nennt Objekte ungeordnet, trotzdem behält jeder gängige Parser die Einfügereihenfolge. Schlüssel zu sortieren ist für alles, was verglichen oder mit einer Prüfsumme versehen wird, dennoch sicherer, denn ein Diff zweier Dokumente, deren Schlüssel nur verschoben wurden, ist unlesbar. Strukturell zu vergleichen, wie es das JSON-Vergleichswerkzeug tut, gleicht nach Schlüssel statt nach Position ab und umgeht die Frage.

Doppelte Schlüssel sind die schärfere Kante. JSON sagt dazu nichts, Parser unterscheiden sich also, und die meisten behalten stillschweigend den letzten Wert. YAML verbietet sie, und viele Parser akzeptieren sie trotzdem. TOML weist sie rundheraus zurück. CSV erlaubt zwei Spalten mit derselben Überschrift und überlässt die Entscheidung dem Leser, Grund genug, eine unbekannte Datei zuerst durch einen CSV-Validator zu schicken. XML ist das einzige Format, in dem Wiederholung bedeutungstragend statt zufällig ist.

Bewusst umwandeln

Jede Pipeline hat einen verlustbehafteten Schritt. Wähle aus, welcher das ist, statt ihn später zu entdecken.

  • Erreiche das engste Format zuletzt. XML nach JSON nach CSV verliert Attribute im ersten Schritt und die Verschachtelung im zweiten. Wenn CSV das Ziel ist, entscheide, welche Felder zählen, und verflache bewusst.
  • Setze Anführungszeichen vor der Umwandlung, nicht danach. Versionsnummern, Ländercodes, Postleitzahlen und alles mit führender Null gehören in der Quelle in Anführungszeichen. Sobald no zu false geworden ist, lässt sich der ursprüngliche Text nicht wiederherstellen.
  • Prüfe die Ausgabe als Daten. Formatiere sie, bestätige die Anzahl der Datensätze und sieh dir eine bekannt heikle Zeile an: das Komma in einem Namen, das längste Array, die größte ID.
  • Vergleiche einen Hin- und Rückweg. Wandle hinaus, wandle zurück, vergleiche. Was der Vergleich meldet, ist genau das, was die Umwandlung nicht tragen kann.
  • Behalte das Original. Die umgewandelte Datei ist ein Derivat, und wenn in drei Monaten jemand fragt, ob ein Feld leer oder nicht vorhanden war, kann nur die Quelle antworten.