Zum Inhalt springen
jsonbeautifiers
Deutsch

JSON, YAML oder TOML: wozu greifen

Diese drei Formate unterscheiden sich weniger darin, was sie ausdrücken können, als darin, wie sie scheitern, und mit den Fehlschlägen werden Sie Ihre Zeit verbringen.

Jede Aussage auf dieser Seite ist entweder gemessen oder belegt. Wo sie keines von beidem ist, steht das dabei.

Eine Deployment-Pipeline liest eine Länderliste aus einer YAML-Datei. Jemand ergänzt Norwegen mit seinem ISO-Code, NO, und die Pipeline überspringt diesen Markt fortan, ohne dass irgendwo ein Fehler auftaucht. Der Wert, der in der Anwendung ankam, war der Boolean false.

So etwas sollte darüber entscheiden, welches Format Sie wählen, und nicht eine Tabelle „unterstützt Kommentare: ja/nein“. Alle drei Formate können eine Abbildung von Zeichenketten auf Werte halten. Getrennt werden sie davon, was sie mit Ihnen anstellen, wenn niemand hinsieht.

JSON: langweilig, und genau darum geht es

JSON ist ein Transportformat. Es hat sechs Typen, vier erlaubte Leerraumzeichen (Leerzeichen, Tabulator, Wagenrücklauf, Zeilenvorschub, gemäß RFC 8259), keine Kommentare, keine nachgestellten Kommas, keinen Datumstyp und genau einen Zahlentyp, den jeder Leser als float64 auffassen darf. An ein paar wichtigen Stellen ist es unterspezifiziert, allen voran bei doppelten Schlüsseln, wo die RFC sagt, Schlüssel SOLLTEN eindeutig sein, und das Verhalten dann undefiniert lässt. JavaScript und Python behalten beide den letzten.

Seine Tugenden sind vollständig nichttechnisch. Jede Sprache liefert einen Parser in ihrer Standardbibliothek. Jeder HTTP-Client weiß, was damit anzufangen ist. Es gibt praktisch keinen Versionsversatz: Ein 2008 geschriebenes JSON-Dokument parst heute überall identisch. Wenn Sie für einen Netzwerksprung, eine Logzeile, eine Nachrichtenwarteschlange oder einen Cache serialisieren, bringt Ihnen keines der menschenzugewandten Merkmale der beiden anderen Formate etwas, und die Universalität bringt Ihnen viel.

Die Fehlermodi sind gut ausgetreten und drehen sich meist um Zahlen. Number.MAX_SAFE_INTEGER ist 9007199254740991, und IDs darüber werden stillschweigend umgeschrieben, was einen eigenen Artikel füllt. Datumsangaben sind per Konvention Zeichenketten, und nichts erzwingt die Konvention, was ebenfalls einen eigenen Artikel füllt. Keines von beiden ist ein Grund, für den Transport ein anderes Format zu wählen. Es sind Gründe, vorsichtig zu sein.

YAML: echte Ergonomie, echte Rechnung

Man wählt YAML nicht, weil es elegant wäre. Man wählt es, weil ein Kubernetes-Manifest oder eine CI-Pipeline etwas ist, das ein Mensch täglich von Hand bearbeitet, und JSON von Hand zu bearbeiten wirklich unangenehm ist: keine Kommentare, Pflichtanführungszeichen und ein fehlendes Komma vierhundert Zeilen weiter oben. YAML gibt Ihnen Kommentare, mehrzeilige Zeichenketten, die lesbar sind, und kein Interpunktionsrauschen. Das ist etwas wert.

Und das ist der Preis.

Das Norwegen-Problem

YAML 1.1 löst unquotiertes no, yes, on, off, y und n als Wahrheitswerte auf. Das Core-Schema von YAML 1.2 tut das nicht und lässt sie Zeichenketten sein. Dasselbe Dokument, derselbe Schlüssel, zwei Antworten:

a: no

Unter der Core-Auflösung von YAML 1.2 ist dieser Wert die Zeichenkette "no". Unter den 1.1-Regeln ist er der Boolean false. Was Sie bekommen, hängt von Ihrer Bibliothek ab, nicht von Ihrer Datei: PyYAML und Rubys Psych lösen nach 1.1 auf, während js-yaml 1.2 folgt. Gos yaml.v3 liegt dazwischen und löst no als Zeichenkette auf, sofern das Zielfeld kein typisierter bool ist, in welchem Fall es die 1.1-Schreibweise weiterhin annimmt. Ein Python-Dienst und ein Node-Dienst, die dieselbe Konfigurationsdatei lesen, sind sich über den Wert uneins, und keiner von beiden protokolliert etwas.

Die Abhilfe ist, jede Zeichenkette zu quoten, die für etwas anderes gehalten werden könnte. Ländercodes, Versionsnummern (1.10 ist ein Float, "1.10" nicht), alles mit führender Null und jeden Wert, den eine Nutzerin liefert. Wenn Sie YAML programmatisch erzeugen, lassen Sie den Emitter defensiv quoten, statt Ihrem eigenen Review zu vertrauen.

Leerraum ist Syntax, und Tabulatoren sind verboten

Die Einrückung trägt die Struktur, eine falsch ausgerichtete Zeile ergibt also ein anderes Dokument statt eines Fehlers. Schlimmer: Die YAML-Spezifikation verbietet Tabulatorzeichen zur Einrückung rundheraus. Ein Editor, der einen Tabulator einfügt, erzeugt eine Datei, die mit einer Meldung über ein Zeichen scheitert, das in Ihrem Terminal unsichtbar ist. Stellen Sie Ihren Editor je Dateityp ein und denken Sie nicht mehr darüber nach.

Anker expandieren auf dem Weg hinaus

Anker und Aliase lassen Sie einen Block einmal definieren und wiederverwenden:

defaults: &defaults
  timeout: 30
  retries: 3

staging:
  <<: *defaults
  host: stage.internal

Das ist das Merkmal, das YAML an Leute verkauft, die vierzig fast identische Dienstdefinitionen pflegen. Es ist auch ein Merkmal, das das Datenmodell nicht hat. Wandeln Sie die Datei nach JSON, dann ist der Merge-Schlüssel aufgelöst, der Alias expandiert, und defaults erscheint vollständig innerhalb von staging. Gehen Sie zurück nach YAML, bekommen Sie zwei wörtliche Kopien. Falsch ist nichts, genau genommen, aber das, was Sie gepflegt haben, ist weg. Eine YAML-Datei, die sich auf Anker stützt, ist nicht wirklich konvertierbar, sie ist nur einmal lesbar.

yaml.load führt Ihre Konfiguration aus

Vollständiges YAML unterstützt sprachspezifische Tags, die beliebige Objekte konstruieren. In Python heißt das, dass ein Dokument mit !!python/object/apply:os.system während des Parsens einen Befehl ausführen kann. yaml.safe_load ist die Fassung, die nur Standardtypen baut, und sie ist die, die Sie für alles wollen, was Sie nicht selbst geschrieben haben. PyYAML hat es schließlich schwer gemacht, das falsch zu tun, indem es ein ausdrückliches Loader-Argument verlangt, aber reichlich Code ist älter, und reichlich andere Sprachen haben eine unsichere Vorgabe noch immer einen Funktionsaufruf weit entfernt.

import yaml

with open("config.yaml") as f:
    cfg = yaml.safe_load(f)   # nicht yaml.load

Das Detail mit der Obermenge

YAML 1.2 wurde als Obermenge von JSON entworfen, und die Spezifikation stellt fest, dass jedes gültige JSON-Dokument auch ein gültiges YAML-1.2-Dokument ist; ein 1.2-Parser liest also Ihr JSON. YAML 1.1 nicht ganz: Es will ein Leerzeichen nach dem Doppelpunkt, ein kompaktes {"a":1} ist dort also ein Parse-Fehler, und die 1.1-Auflösungsregeln machen weiterhin aus manchen Ihrer Zeichenketten Wahrheitswerte. Wenn Sie sich auf „einfach das JSON dem YAML-Parser geben“ verlassen, prüfen Sie zuerst, welche Version Ihre Bibliothek umsetzt. In jedem Fall kommen Sie sauber in die andere Richtung mit dem YAML-zu-JSON-Konverter.

TOML: eindeutig, bis es verschachtelt

TOML gibt es, weil INI-Dateien angenehm und ungenau waren. Es behebt die Ungenauigkeit: Ganzzahlen und Fließkommazahlen sind eigene Typen, Wahrheitswerte sind nur true und false, und es gibt vier echte Datums- und Zeittypen (Datum-Zeit mit Offset, lokale Datum-Zeit, lokales Datum, lokale Zeit), eingebaut in die Grammatik statt durch Zeichenketten geschmuggelt. Kommentare sind erstklassig. Denselben Schlüssel zweimal zu definieren ist ein harter Fehler statt undefiniertes Verhalten, eine Kleinigkeit, die eine echte Klasse von Merge-Fehlern fängt.

Für eine flache oder flach verschachtelte Konfiguration ist es das beste der drei. Cargo.toml und pyproject.toml sind die naheliegenden Fälle: ein paar Abschnitte, Zeichenketten- und Listenwerte, gelegentlich eine Ebene Verschachtelung. Nichts ist mehrdeutig, und nichts braucht Anführungszeichen zur Sicherheit.

Es wird schnell hässlich, wenn die Daten ein Baum sind. Tiefe Verschachtelung bedeutet entweder lange gepunktete Überschriften oder lange gepunktete Schlüssel:

[servers.production.database.replica]
host = "10.0.0.4"
port = 5432

Und ein Array von Objekten braucht die doppelt eingeklammerte Array-of-Tables-Form, je Element wiederholt:

[[targets]]
name = "web"
port = 8080

[[targets]]
name = "worker"
port = 8081

Bei zwei Einträgen liest sich das gut. Bei dreißig Einträgen mit je drei Feldern und Inline-Tabellen, die in eine Zeile passen müssen, kämpfen Sie gegen das Format. Wenn Ihre Konfiguration wirklich hierarchisch ist, hat TOML die falsche Form, und Sie spüren es bei jeder zusätzlichen Ebene.

Was Ihnen keines von ihnen gibt

Einen Dezimaltyp. Alle drei geben Ihnen ein Float, also eine binäre Näherung. Geld gehört weiterhin als Ganzzahl in Untereinheiten oder in eine Zeichenkette.

Binärdaten. JSON und TOML haben überhaupt keine Darstellung, also Base64 in einer Zeichenkette. YAML hat ein !!binary-Tag, das funktioniert und die Umwandlung in eines der beiden anderen nicht übersteht.

Ein Schema, das mit dem Format kommt. JSON Schema ist die reife Option, und da YAML 1.2 auf dasselbe Datenmodell abbildet, können Sie damit auch YAML validieren. So funktioniert die meiste YAML-Validierung in der Praxis. TOML hat kein Gegenstück mit vergleichbarer Verbreitung.

Kommentare durch eine Konvertierung hindurch. Das ist die Einbahntür. Kommentare leben in der Syntax, nicht im Datenmodell, eine nach JSON gewandelte YAML- oder TOML-Datei verliert also dauerhaft jeden Kommentar, und es gibt kein cleveres Werkzeug, das sie zurückholt. Wenn die Kommentare einer Datei tragend sind, ist die Quelle der Wahrheit eben diese Datei, und JSON ist nur ein erzeugtes Artefakt. Dass JSON keine Kommentare hat, ist Absicht, und das ist der Grund für diese Asymmetrie.

Auswählen, als Fragen

Ist eine Maschine die einzige Leserin? JSON. Lassen Sie eine API nicht YAML sprechen.

Bearbeitet ein Mensch sie wöchentlich, und ist sie hierarchisch? YAML, mit Quoting-Disziplin und safe_load.

Bearbeitet ein Mensch sie, und besteht sie überwiegend aus flachen Abschnitten mit Skalaren? TOML. Sie verlieren nichts und gewinnen eindeutige Typen.

Müssen Kommentare überleben? Was auch immer Sie wählen: Diese Datei ist die Quelle der Wahrheit. Erzeugen Sie abwärts, bearbeiten Sie nie die erzeugte Kopie.

Erzeugen Nichtentwickler oder eine Oberfläche die Werte? JSON, von einem Programm erzeugt, gegen ein Schema validiert. Jede YAML-Falle oben wird von einer Zeichenkette ausgelöst, die jemand getippt hat.

Wandeln Sie gerade zwischen ihnen um? Tun Sie es im JSON-zu-YAML-Konverter und lesen Sie die Ausgabe, statt ihr zu vertrauen, besonders die Wahrheitswerte, und schicken Sie das Ergebnis durch den Validator, bevor es irgendetwas erreicht, das deployt.