JSON Toolkit

JSON zu YAML in der Kommandozeile: Die vier Fallen hinter einem yq-Befehl

Fazit: Der am häufigsten kopierte Befehl aus Tutorials, yq -P input.json > output.yaml, gibt unter yq v4 immer noch JSON aus. Dieser Artikel misst yq v4.44.6, PyYAML 6.0.3 und js-yaml an einem 2.39 MB großen Beispiel, erklärt, warum die Eingabeendung das Ausgabeformat bestimmt, und zeigt die korrekten Schreibweisen für Massenkonvertierung, Quoting, Einrückung und die Präzision großer Ganzzahlen.

Fortgeschritten~9 Min. LesezeitVeröffentlicht 2026-11-03Aktualisiert 2026-11-03
Titelbild, das einen JSON-zu-YAML-Befehl und seine Ausgabe im Terminal vergleicht

Kurz gesagt: yq -P input.json > output.yaml — der in Tutorials am häufigsten kopierte Befehl — liefert unter yq v4 immer noch JSON. Um YAML zu bekommen, müssen Sie -o=yaml (oder -oy) explizit angeben. -P ist nur die Kurzform von --prettyPrint: Es steuert Einrückung und Blockstil, nicht das Ausgabeformat.

JSON nach YAML zu konvertieren war nie so einfach wie „die Dateiendung ändern". Die eigentlichen Kosten verstecken sich an vier Stellen: Die Eingabeendung entscheidet über das Ausgabeformat, die Quoting-Regeln schreiben Ihre Strings stillschweigend um, die Einrückung von Sequenzen beeinflusst nachgelagerte Werkzeuge, und eine große Ganzzahl verliert ihre Präzision für immer, sobald sie durch yq gelaufen ist.

1. Führen wir zuerst den populärsten Befehl aus

Auf einer sauberen Maschine mit installiertem yq v4.44.6 erzeugen wir eine minimale Eingabe:

printf '{"a":1,"b":{"c":2}}' > s.json
yq -P '.' s.json

Zurück kommt kein YAML, sondern eingerücktes JSON:

{
  "a": 1,
  "b": {
    "c": 2
  }
}

Wenn Sie dem Tutorial folgen und diese Ausgabe nach output.yaml umleiten, erhalten Sie eine Datei mit der Endung .yaml und dem Inhalt JSON. Sie lässt sich von jedem YAML-1.2-Parser problemlos lesen (in YAML 1.2 ist JSON eine Teilmenge; die vollständige Herleitung dieser Enthaltenseins-Beziehung steht in den Grenzen der Konvertierung JSON ↔ YAML und XML), kurzfristig bricht also nichts — bis eines Tages jemand einen Kommentar oder einen Anker ergänzt oder das yamllint in der CI eine Wand von truthy value should be one of ausgibt und die Fehlersuche beginnt.

Der Aufruf, der wirklich YAML erzeugt, benennt das Ausgabeformat:

yq -o=yaml '.' s.json
# a: 1
# b:
#   c: 2

2. Warum das so ist: Die Endung entscheidet über das Ausgabeformat

yq v4 verhält sich nach der Regel „Eingabeformat entscheidet über Ausgabeformat". Es leitet das Format aus der Endung der Eingabedatei ab und übernimmt ohne -o dieses Ergebnis. .json hinein → JSON hinaus. .yaml hinein → YAML hinaus. Liest es von stdin ohne Endung, gilt standardmäßig YAML.

Dieselbe Eingabe, vier Schreibweisen, gemessen:

Befehl Quelle der Eingabe Tatsächliches Ausgabeformat Ergebnis
yq -P '.' s.json Datei s.json JSON (eingerückt) Falle, wenn nach .yaml umgeleitet
yq -o=yaml '.' s.json Datei s.json YAML (Blockstil) korrekt
yq -P -o=yaml '.' s.json Datei s.json YAML (Blockstil) korrekt, -P hier überflüssig
cat s.json | yq -P '.' stdin, ohne Endung YAML (Blockstil) korrekt, weil YAML der Standard ist

Die dritte Zeile sagt eines: -P ist nicht immer nötig, wenn -o=yaml schon gesetzt ist — und harmlos ist es trotzdem nicht, wie die Laufzeiten in Abschnitt 6 zeigen. Die vierte Zeile erklärt, warum „ich habe es einfach abgeschrieben und es funktioniert" so oft stimmt: Viele füttern yq über eine Pipe und umgehen damit genau diesen Endungs-Zweig.

3. Massenkonvertierung: drei Shell-Schreibweisen

Eine einzelne Datei zu konvertieren ist nur der Anfang. Fast jeder ernsthafte Grund, das auf der Kommandozeile zu tun, ist ein Massenfall: alle Konfigurationen eines Verzeichnisses, alle aufgezeichneten API-Snapshots auf einmal nach YAML bringen.

bash-/zsh-Schleife (am lesbarsten, für ein einzelnes Verzeichnis):

for f in configs/*.json; do
  yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done

find, rekursiv (wenn Unterverzeichnisse mitzählen):

find . -name '*.json' -exec sh -c 'yq -o=yaml "." "$1" > "${1%.json}.yaml"' _ {} \;

PowerShell (auf Windows-CI):

Get-ChildItem *.json | ForEach-Object { yq -o=yaml '.' $_.FullName > ($_.BaseName + '.yaml') }

Alle drei Schreibweisen haben dieselbe harte Voraussetzung: Prüfen Sie zuerst, dass das Quell-JSON gültig ist. Ein Konverter ist kein Parser: Bei einem Syntaxfehler liefern viele Konverter nur eine vage Position, statt wie ein Validator die Zeilennummern sauber aufzulisten. Eine vorgeschaltete Prüfung in der Schleife kostet fast nichts:

for f in configs/*.json; do
  jq empty "$f" || { echo "invalid: $f"; continue; }
  yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done

jq empty parst nur, ohne etwas auszugeben, und liefert bei ungültigem JSON einen Exit-Code ungleich null. Zusammen mit set -euo pipefail stoppt die ganze Pipeline bei der ersten kaputten Datei, statt einen Haufen halb konvertierter Ergebnisse zu produzieren.

4. Vier Ausgabe-Parameter, vier getrennte Aufgaben

Die Parameterliste von yq ist lang, aber auf dem Weg von JSON zu YAML müssen Sie sich nur vier merken:

Parameter Wirkung Wann er zwingend ist
-o=yaml / -oy setzt das Ausgabeformat auf YAML immer, wenn die Eingabe eine .json-Datei ist
-P / --prettyPrint erzwingt Blockstil und normalisiert die Einrückung beim Lesen von stdin oder wenn die Sequenzeinrückung zählt
-I=n Anzahl der Einrückungs-Leerzeichen, Standard 2 wenn die Gegenseite 4 Leerzeichen verlangt (manche Ansible-Stilrichtlinien)
--split-exp zerlegt Array-Elemente in mehrere Dokumente beim Erzeugen von Kubernetes-Manifesten mit mehreren Ressourcen

Der Punkt mit den mehreren Dokumenten verdient einen eigenen Absatz. Kubernetes-Manifeste trennen mehrere Ressourcen üblicherweise mit ---. Erzeugt man das aus einem JSON-Array:

yq -P -o=yaml '.[] | splitDoc' --split-exp 'true' kubernetes-list.json

Ohne --split-exp erhalten Sie eine YAML-Sequenz; mit der Option wird jedes Array-Element zu einem eigenen, durch --- getrennten Dokument. kubectl apply schluckt beide Dateien, aber die Lesbarkeit des Diffs unterscheidet sich um eine Größenordnung.

5. Quoting: sechs String-Formen, die in Anführungszeichen müssen

Die Typinferenz von YAML ist implizit: Ein String, der wie eine Zahl oder ein Boolean aussieht, wird als solcher geparst, wenn er nicht in Anführungszeichen steht. Das ist kein theoretisches Risiko — gemessen auf yq v4.44.6 werden die folgenden sechs Werte automatisch gequotet, was beweist, dass der Parser sie tatsächlich falsch lesen würde:

printf '{"version":"1.0","enabled":"true","port":"8080","empty":"","colon":"host: value","hash":"v # c","unicode":"端口 8080","ts":"2026-11-03"}' | yq -P -o=yaml '.'
version: "1.0"
enabled: "true"
port: "8080"
empty: ""
colon: 'host: value'
hash: 'v # c'
unicode: 端口 8080
ts: "2026-11-03"

Die Folgen, einzeln:

Ursprungswert (JSON-String) Ohne Anführungszeichen gelesen als Reale Auswirkung
"1.0" Float 1.0 Die Version wird eine Dezimalzahl; 1.10 wird zu 1.1
"true" Boolean true Verzweigungslogik kippt oder die Typen passen nicht mehr
"8080" Integer 8080 Der Port wechselt den Typ, der String-Vergleich schlägt fehl
"" null Der leere String verschwindet, die Gegenseite bekommt None
"host: value" Parse-Fehler oder verschachteltes Mapping Nach einem Doppelpunkt muss ein Leerzeichen folgen
"2026-11-03" Datums-Objekt Deserialisiert zu datetime.date statt zu einem String

Der letzte Punkt ist der Hinterhältigste. In JSON ist ein Datum nur ein String; nach der Konvertierung wird daraus ein YAML-Datumstyp, und beim Zurückkonvertieren geben viele Implementierungen ein Datums-Objekt im ISO-Format aus statt des ursprünglichen Strings — damit ist die Roundtrip-Konsistenz dahin. Sowohl PyYAML als auch yq setzen hier Anführungszeichen, was einem Eingeständnis gleichkommt, dass das Risiko real ist.

Nebenbei bemerkt: Die Zeile unicode: 端口 8080 wird nicht gequotet — chinesische Zeichen sind in YAML sicher. Sorgen müssen Sie sich nicht darum machen, sondern um die Schlüsselreihenfolge und die Präzision, die im nächsten Abschnitt kommen.

6. Python- und Node-Entsprechungen mit gemessenen Laufzeiten

yq lässt sich nicht überall installieren: Offline-Umgebungen, abgeriegelte CI-Images, Windows ohne Paketmanager. Die Entsprechungen in Python und Node existieren, jede mit ihren eigenen Standardwerten, die zubeißen wollen. Wie Sie zwischen den drei Wegen szenarienabhängig wählen, zeigt die Auswahl von JSON-Kommandozeilen-Werkzeugen und Editoren in einer vollständigen Vergleichstabelle; dieser Artikel bleibt bei der einen Richtung JSON → YAML.

Python + PyYAML (drei Argumente, keines davon optional):

import json, yaml

with open("config.json", encoding="utf-8") as f:
    data = json.load(f)

with open("config.yaml", "w", encoding="utf-8") as f:
    yaml.safe_dump(
        data, f,
        default_flow_style=False,  # Blockstil, nicht {a: 1, b: 2}
        allow_unicode=True,        # Chinesisch nicht zu \u7AEF\u53E3 escapen
        sort_keys=False,           # ursprüngliche Schlüsselreihenfolge des JSON behalten
    )

Diese drei stehen standardmäßig auf False / False / True, das heißt: PyYAML sortiert die Schlüssel unmittelbar nach dem Auspacken alphabetisch um und wandelt Chinesisch in \uXXXX. Dieselbe Eingabe, mit Standardwerten gemessen, ergibt:

apiVersion: v1
data:
  LOG_LEVEL: info
  colon: 'host: value'
  unicode: "\u7AEF\u53E3 8080"
kind: ConfigMap
metadata:
  labels:
    app: web
  name: app-config

Beachten Sie, dass kind nun nach apiVersion und vor metadata einsortiert ist. In Kubernetes-Manifesten ist es Community-Konvention, dass apiVersion vor kind steht; eine durcheinandergebrachte Schlüsselreihenfolge bricht zwar das Parsen nicht, macht aber das Review zur Qual.

Node + js-yaml:

const fs = require("fs");
const yaml = require("js-yaml");

const data = JSON.parse(fs.readFileSync("config.json", "utf8"));
fs.writeFileSync(
  "config.yaml",
  yaml.dump(data, { lineWidth: -1, noRefs: true, sortKeys: false })
);

lineWidth: -1 schaltet das Umbrechen langer Zeilen ab (sonst werden lange URLs mitten im String getrennt), und noRefs: true unterbindet Anker und Aliase — Pflicht, wenn der Abnehmer kubectl oder ein anderes Werkzeug ist, das sie nicht auflöst.

Gemessene Laufzeiten (Beispiel: 2.39 MB JSON, 12000 Datensätze, jeder mit einem verschachtelten Objekt und zwei Arrays; Maschine ist die für diese Aufgabe genutzte Sandbox-Umgebung):

Implementierung Konvertierung Rückwärts-Parsen Roundtrip
yq -o=yaml v4.44.6 0.45 s 0.45 s identisch
yq -P -o=yaml v4.44.6 4.16 s — identisch
Node js-yaml 4.x 0.23 s 0.11 s identisch
Python PyYAML 6.0.3 3.26 s 8.01 s identisch

Zwei kontraintuitive Ergebnisse. Erstens: Mit -P wird yq etwa 9-mal langsamer (0.45 s → 4.16 s), obwohl die Ausgabe auf diesem Beispiel byte-identisch ist (2,326,895 Bytes in beiden Fällen) — -P führt einen vollständigen Neu-Layout-Durchlauf durch, und der summiert sich, wenn Sie mehrere hundert Dateien konvertieren; sparen Sie ihn sich, wo es geht. Zweitens: Das rückwärtige Parsen von PyYAML (8.01 s) ist mehr als doppelt so langsam wie das Konvertieren in Vorwärtsrichtung (3.26 s) — wenn Ihre Pipeline YAML wiederholt zurücklesen muss, ist Python keine gute Wahl.

Noch eine Zahl: Aus 2.39 MB JSON werden 2.33 MB YAML im Blockstil, also rund 3 % weniger. Die verbreitete Behauptung „YAML ist immer größer als JSON" stimmt nicht — bei tiefer Verschachtelung können die Anführungszeichen, Kommas und geschweiften Klammern von JSON mehr Platz brauchen als die Einrückungs-Leerzeichen von YAML.

7. Drei Dinge, die nicht zurückkommen

Die vorigen Abschnitte handelten davon, richtig zu konvertieren. Dieser handelt davon, was verloren geht. Gemessen an einer eigens konstruierten Eingabe:

printf '{"order_id":12345678901234567890,"zip":"0150","ts":"2026-11-03","f":1.0e6}' > t.json
yq -o=yaml '.' t.json

Die Ausgabe von yq:

order_id: 1.2345678901234567e+19
zip: "0150"
ts: "2026-11-03"
f: 1000000

Und zurück nach JSON: {"order_id":12345678901234567000,"zip":"0150","ts":"2026-11-03","f":1000000}.

Die 20-stellige Bestellnummer 12345678901234567890 ist zu 12345678901234567000 geworden — die letzten drei Stellen auf null gesetzt, ohne jede Warnung. Der Grund: yq parst über den float64-Pfad von Go; Ganzzahlen jenseits von 2^53 lassen sich nicht exakt darstellen, also wird in wissenschaftlicher Notation ausgegeben, und beim Zurückkonvertieren ist der Originalwert nicht mehr erreichbar.

Dieselbe Eingabe an PyYAML übergeben:

order_id: 12345678901234567890
zip: '0150'
ts: '2026-11-03'
f: 1000000.0

Python-Ganzzahlen haben beliebige Präzision, alle 20 Stellen bleiben erhalten, und das Zurücklesen ergibt wieder 12345678901234567890. Wenn Ihre Daten Bestellnummern, Snowflake-IDs oder Ausweisnummern enthalten: Benutzen Sie nicht yq, sondern Python, oder machen Sie das Feld vorher zu einem String. Der vollständige Mechanismus hinter dieser Klasse von Präzisionsproblemen ist in Warum große Ganzzahlen in JSON ihre Präzision verlieren erklärt.

Das zweite, was verloren geht, ist die Unterscheidung zwischen float und int: Aus 1.0e6 wird in der yq-Ausgabe 1000000 (eine Ganzzahl), während PyYAML 1000000.0 behält. Das dritte ist die Schlüsselreihenfolge — PyYAML sortiert standardmäßig, yq und js-yaml behalten standardmäßig, und das ist in sprachübergreifenden Teams eine versteckte Quelle von Uneinigkeit.

8. In der CI nicht nur konvertieren: validieren, konvertieren, linten

Die Konvertierung als einzigen Schritt zu behandeln, ist der häufigste Pipeline-Defekt. Ein Exit-Code 0 nach der Konvertierung bedeutet nur „das Eingabe-JSON ließ sich parsen", nicht „die Ausgabe ist korrekt" und auch nicht „die Ausgabe erfüllt die Anforderungen des Abnehmers". Die korrekte Form hat drei Stufen:

#!/usr/bin/env bash
set -euo pipefail

# 1. Quelle validieren: Syntax-Ebene
jq empty deployment.json

# 2. Konvertieren: Ausgabeformat explizit benennen
yq -o=yaml '.' deployment.json > deployment.yaml

# 3. Ergebnis validieren: von einem YAML-Parser lesbar und lint-clean
yq -o=json -I=0 '.' deployment.yaml > /dev/null
yamllint deployment.yaml

Dieses yq -o=json ... > /dev/null in Schritt 3 wirkt überflüssig und ist die nützlichste Zeile des ganzen Blocks: Es zwingt dazu, das Ergebnis erneut zu parsen. Einrückungsfehler, fehlgeschlagene Anker-Auflösung und umgeschriebene Typen fallen genau hier auf. Lassen Sie die Zeile weg, wartet das Problem bis kubectl apply oder bis zum Anwendungsstart.

Wenn das Ergebnis eine von Menschen gepflegte Konfiguration ist (Helm-values, docker-compose, GitHub-Actions-Workflows), prüfen Sie zusätzlich drei Dinge: ob die Schlüsselreihenfolge durcheinandergebracht wurde, ob lange Zahlen Stellen verloren haben und ob Kommentare noch von Hand ergänzt werden müssen — JSON kennt keine Kommentare, jede Anmerkung kann also erst nach der Konvertierung geschrieben werden; kein Konverter erfindet sie für Sie.

FAQ

yq -P oder -o=yaml — was soll ich schreiben?

-o=yaml entscheidet, ob die Ausgabe YAML ist; -P entscheidet, wie das YAML aussieht. Bei einer .json-Eingabe ist -o=yaml Pflicht, und -P kommt nur dazu, wenn Sie den Blockstil oder die Sequenzeinrückung erzwingen wollen. Beim Lesen von stdin ohne Endung gibt yq ohnehin standardmäßig YAML aus, dann genügt -P allein.

Kann ich das konvertierte YAML direkt an Kubernetes verfüttern?

Syntaktisch ja, unter drei Vorbehalten: Schlüssel mit Reihenfolgekonvention wie apiVersion wurden nicht umsortiert (PyYAML sortiert standardmäßig, man braucht sort_keys=False), es wurden keine Anker oder Aliase erzeugt (js-yaml: noRefs: true) und die langen Zahlen haben ihre Stellen behalten. Für Manifeste mit mehreren Ressourcen zerlegen Sie das Ganze außerdem mit --split-exp in durch --- getrennte Dokumente.

Warum ist meine Ausgabe eine einzige Zeile, {a: 1, b: 2}, statt eingerückt?

Das ist Flow-Stil. Er ist gültiges YAML, macht aber den Sinn der Konvertierung zunichte. Nutzen Sie -P, um den Blockstil zu erzwingen; bei PyYAML prüfen Sie default_flow_style=False (seit PyYAML 5.1 ist False der Standard, ältere Versionen brauchen es explizit).

Meine 20-stellige Bestellnummer hat ihre letzten Stellen verloren. Kann ich sie retten?

Nein — verhindern ist die einzige Option. yq parst über float64, deshalb verlieren Ganzzahlen jenseits von 2^53 konstruktionsbedingt an Präzision. Entweder Sie wechseln zu Python (Ganzzahlen mit beliebiger Präzision, nachweislich verlustfrei) oder Sie speichern lange Zahlen im JSON als Strings — letzteres ist sicherer, weil es zugleich das entsprechende Problem von JSON.parse auf der JavaScript-Seite löst.

Online-Konverter oder Kommandozeile?

Für geheimhaltungsbedürftige Konfigurationen und API-Snapshots mit Tokens: Kommandozeile oder ein reines Frontend-Werkzeug, nichts hochladen. Für einmalige, kleine Mengen, bei denen Sie eine visuelle Bestätigung wollen, ist ein Online-Werkzeug schneller; die Seite /convert/yaml dieser Website läuft vollständig im Browser, die Daten verlassen Ihren Rechner nicht. Für alles, was massenhaft, reproduzierbar oder CI-gebunden ist: Kommandozeile, und schreiben Sie die Validierungsschritte gleich mit ins Skript.