JSON na YAML v příkazové řádce: čtyři pasti jednoho příkazu yq
Shrnutí: Nejčastěji kopírovaný příkaz z návodů, yq -P input.json > output.yaml, na yq v4 pořád vydává JSON. Článek měří yq v4.44.6, PyYAML 6.0.3 a js-yaml na vzorku 2.39 MB, vysvětluje, proč formát výstupu určuje přípona vstupu, a dává správné zápisy pro dávkový převod, uvozovky, odsazení a přesnost velkých celých čísel.

Stručně: yq -P input.json > output.yaml — nejčastěji kopírovaný příkaz v návodech — na yq v4 pořád produkuje JSON. Chcete-li YAML, musíte výslovně uvést -o=yaml (nebo -oy). -P je jen zkratka pro --prettyPrint: řídí odsazení a blokový styl, ne formát výstupu.
Převést JSON na YAML nikdy neznamenalo jen vyměnit příponu souboru. Skutečná cena se skrývá na čtyřech místech: formát výstupu určuje přípona vstupu, pravidla uvozovek tiše přepisují vaše řetězce, odsazení sekvencí ovlivňuje navazující nástroje a velké celé číslo, které jednou projde přes yq, ztratí přesnost navždy.
1. Nejdřív si spustíme ten nejpopulárnější příkaz
Na čistém stroji s nainstalovaným yq v4.44.6 vytvoříme minimální vstup:
printf '{"a":1,"b":{"c":2}}' > s.json
yq -P '.' s.json
Co se vrátí, není YAML, ale odsazený JSON:
{
"a": 1,
"b": {
"c": 2
}
}
Když podle návodu přesměrujete výstup do output.yaml, dostanete soubor s příponou .yaml a obsahem JSON. Každý parser YAML 1.2 ho přečte bez problémů (JSON je podmnožinou YAML 1.2; celý důkaz tohoto vztahu je v hranicích převodu JSON, YAML a XML), takže se hned nic nerozbije. Rozbije se to v den, kdy někdo přidá komentář nebo kotvu, nebo když yamllint v CI začne chrlit celou stěnu hlášek truthy value should be one of. Ve sdílených repozitářích je to obzvlášť nepříjemné: takový soubor prochází revizí měsíce, protože vypadá jako obyčejný YAML, a chyba vyplave až při první změně struktury, kdy už nikdo nemá původní JSON po ruce.
Příkaz, který skutečně produkuje YAML, výslovně jmenuje formát výstupu:
yq -o=yaml '.' s.json
# a: 1
# b:
# c: 2
2. Proč se to děje: přípona určuje formát výstupu
yq v4 se řídí pravidlem „formát vstupu určuje formát výstupu". Formát odhadne z přípony vstupního souboru a bez -o tento odhad znovu použije. .json na vstupu dává JSON na výstupu, .yaml na vstupu dává YAML na výstupu a při čtení ze stdin bez přípony nastoupí výchozí YAML.
Stejný vstup, čtyři zápisy, naměřené výsledky:
| Příkaz | Zdroj vstupu | Skutečný formát výstupu | Verdikt |
|---|---|---|---|
yq -P '.' s.json |
soubor s.json |
JSON (odsazený) | past při přesměrování do .yaml |
yq -o=yaml '.' s.json |
soubor s.json |
YAML (blokový styl) | správně |
yq -P -o=yaml '.' s.json |
soubor s.json |
YAML (blokový styl) | správně, -P je zde navíc |
cat s.json | yq -P '.' |
stdin, bez přípony | YAML (blokový styl) | správně, výchozí je YAML |
Třetí řádek říká: -P není vždy nutné, když už je -o=yaml, ale není ani neškodné — viz naměřené časy v šesté části. Čtvrtý vysvětluje, proč „zkopíroval jsem to a funguje to": spousta lidí posílá data rourou a tím náhodou obejde větev, kde rozhoduje přípona. Přenesete-li takový příkaz z interaktivního terminálu do skriptu s explicitním názvem souboru, chování se obrátí. Právě proto se vyplatí psát -o=yaml vždy výslovně, i když dnes data tečou rourou: výsledek pak nezávisí na tom, jak příště vstup připojíte.
3. Dávkový převod: tři shellové zápisy
Převod jednoho souboru je jen začátek. Téměř každý skutečný důvod dělat to v příkazové řádce je dávkový: všechny konfigurace v adresáři, všechny snímky rozhraní, všechno najednou.
Smyčka bash / zsh (nejčitelnější, pro jeden adresář):
for f in configs/*.json; do
yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done
find rekurzivně (když záleží i na podadresářích):
find . -name '*.json' -exec sh -c 'yq -o=yaml "." "$1" > "${1%.json}.yaml"' _ {} \;
PowerShell (na Windows CI):
Get-ChildItem *.json | ForEach-Object { yq -o=yaml '.' $_.FullName > ($_.BaseName + '.yaml') }
Všechny tři sdílejí jeden tvrdý předpoklad: nejdřív ověřte, že je zdrojový JSON platný. Konvertor není validátor: při syntaktické chybě mnoho konvertorů vydá jen mlhavou pozici místo přesného čísla řádku, které dává validátor. Přidat jednu kontrolu dovnitř smyčky stojí téměř nic:
for f in configs/*.json; do
jq empty "$f" || { echo "invalid: $f"; continue; }
yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done
jq empty jen parsuje a nic nevypisuje; na neplatném JSON vrátí nenulový exit kód. Spolu se set -euo pipefail pipeline zastaví na prvním špatném souboru místo toho, aby vyrobila hromadu napůl převedených výstupů. U nočních buildů, které zpracovávají stovky konfigurací, to znamená rozdíl mezi jednou srozumitelnou chybou v logu a desítkami rozbitých YAML, které pak musí někdo ručně uklízet. Vyplatí se také předem rozhodnout, co s již existujícími soubory .yaml: smyčka je bez varování přepíše, takže v repozitářích s mixem formátů je rozumné před přesměrováním výstupu přidat kontrolu existence.
4. Čtyři přepínače výstupu, čtyři odlišné úlohy
Seznam přepínačů yq je dlouhý, ale na cestě JSON → YAML záleží jen na čtyřech:
| Přepínač | Co dělá | Kdy ho musíte uvést |
|---|---|---|
-o=yaml / -oy |
nastaví formát výstupu na YAML | kdykoli je vstupem soubor .json |
-P / --prettyPrint |
vynutí blokový styl a srovná odsazení | při čtení ze stdin nebo když záleží na odsazení sekvencí |
-I=n |
šířka odsazení, výchozí 2 | navazující nástroj chce 4 mezery (některé stylové příručky Ansible) |
--split-exp |
rozdělí prvky pole na samostatné dokumenty | při generování manifestů Kubernetes s více zdroji |
--split-exp si zaslouží zvláštní poznámku. Manifesty Kubernetes obvykle oddělují jednotlivé zdroje značkou ---. Při generování takového souboru z pole JSON:
yq -P -o=yaml '.[] | splitDoc' --split-exp 'true' kubernetes-list.json
Bez --split-exp dostanete jednu YAML sekvenci; s ním se každý prvek pole stane samostatným dokumentem odděleným ---. kubectl apply přijme oba soubory, ale čitelnost diff se liší o celý řád. V týmech, kde manifesty před nasazením čtou lidé, právě to rozhodne, zda si někdo všimne navíc pole v resources dřív, než se dostane do clusteru.
5. Uvozovky: šest tvarů řetězců, které musí být v uvozovkách
Odvozování typů v YAML je implicitní: řetězcová hodnota, která vypadá jako číslo nebo boolean, se bez uvozovek načte jako jiný typ. Není to teoretické riziko — na yq v4.44.6 se všech šest níže uvedených hodnot automaticky opatří uvozovkami, což dokazuje, že by je parser skutečně přečetl špatně:
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"
Důsledky, jeden po druhém:
| Původní hodnota (řetězec JSON) | Bez uvozovek načteno jako | Reálný dopad |
|---|---|---|
"1.0" |
desetinné číslo 1.0 |
číslo verze se stane zlomkem, 1.10 se scvrkne na 1.1 |
"true" |
boolean true |
větvení se obrátí nebo typy nebudou sedět |
"8080" |
celé číslo 8080 |
změní se typ portu, porovnání řetězců selže |
"" |
null |
prázdný řetězec zmizí, downstream dostane None |
"host: value" |
chyba parsování nebo vnořená mapa | za dvojtečkou musí následovat mezera |
"2026-11-03" |
objekt data | deserializuje se na datetime.date, ne na řetězec |
Poslední případ je nejzáludnější. V JSON je datum jen řetězec; po převodu se z něj stane datum YAML a při zpětném převodu mnoho implementací serializuje objekt data ve formátu ISO místo původního řetězce, takže konzistence obousměrného převodu se rozpadne. PyYAML i yq těmto hodnotám uvozovky přidávají právě proto: fakticky přiznávají, že riziko je reálné. Nejčastěji se to projeví u konfigurací s poli typu release_date nebo valid_until, která změní typ tiše, bez jediného varování v logu buildu, a odhalí to až aplikační kód očekávající řetězec.
A ještě jedna poznámka: řádek unicode: 端口 8080 vychází bez uvozovek — čínské znaky jsou v YAML bezpečné. Starosti by vám mělo dělat pořadí klíčů a přesnost čísel, o nichž pojednávají další části.
6. Ekvivalenty v Pythonu a Node s naměřenými časy
yq nejde nainstalovat všude: stroje bez přístupu k síti, omezené CI image, Windows bez správce balíčků. Ekvivalenty v Pythonu a Node existují a každý má své výchozí hodnoty, které čekají na příležitost uštknout. Úplnou srovnávací tabulku všech tří cest podle scénářů najdete v výběru nástrojů příkazové řádky JSON a editorů; tento článek zůstává jen u směru JSON → YAML.
Python + PyYAML (tři argumenty, žádný nelze vynechat):
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, # block style, not {a: 1, b: 2}
allow_unicode=True, # keep CJK as-is, not \u7AEF\u53E3
sort_keys=False, # preserve the original key order
)
Tyto tři argumenty mají výchozí hodnoty False / False / True, takže PyYAML „po vybalení" řadí klíče podle abecedy a čínské znaky escapuje na \uXXXX. Se stejným vstupem a výchozími hodnotami vznikne:
apiVersion: v1
data:
LOG_LEVEL: info
colon: 'host: value'
unicode: "\u7AEF\u53E3 8080"
kind: ConfigMap
metadata:
labels:
app: web
name: app-config
Všimněte si, že kind je nyní za apiVersion, ale před metadata. V manifestech Kubernetes je konvence, že apiVersion předchází kind; přeuspořádání parsování nerozbije, ale revize je kvůli němu utrpením. V týmech, kde se manifesty na každém pull requestu čtou očima, se to rychle mění na zdroj nesmyslných diff: mění se jen řádky s klíči, zatímco podstata konfigurace zůstává stejná.
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 vypne zalamování dlouhých řádků (jinak se dlouhé URL rozlomí uprostřed) a noRefs: true potlačí kotvy a aliasy — je to nutné, když je konzumentem kubectl nebo jiný nástroj, který kotvy neumí vyřešit. Obě volby patří ke stejné sadě výchozích hodnot jako sortKeys: false: všechny tři je třeba uvést výslovně, protože knihovna je laděná na kompaktní výstup, ne na konfiguraci určenou pro lidské čtení.
Naměřené časy (vzorek: JSON o velikosti 2.39 MB, 12000 záznamů, každý s vnořeným objektem a dvěma poli; stroj je sandbox použitý pro tento úkol):
| Implementace | Převod | Zpětný parsing | Obousměrně |
|---|---|---|---|
yq -o=yaml v4.44.6 |
0.45 s | 0.45 s | shodné |
yq -P -o=yaml v4.44.6 |
4.16 s | — | shodné |
| Node js-yaml 4.x | 0.23 s | 0.11 s | shodné |
| Python PyYAML 6.0.3 | 3.26 s | 8.01 s | shodné |
Dva neintuitivní výsledky. Za prvé, přidání -P zpomalí yq zhruba devětkrát (0.45 s → 4.16 s), přestože je na tomto vzorku výstup byte-identický (2,326,895 bajtů v obou případech) — -P dělá celé jedno kolo přeformátování a při dávkovém převodu několika set souborů se to nasčítá. Za druhé, zpětný parsing PyYAML (8.01 s) je víc než dvakrát pomalejší než dopředný dump (3.26 s), takže Python je špatná volba, pokud vaše pipeline YAML opakovaně načítá zpět.
Ještě jedno pozorování: z 2.39 MB JSON se po převodu do blokového YAML stalo 2.33 MB, tedy asi o 3 % méně. Rozšířená poučka „YAML je vždy větší než JSON" neplatí — ve velké hloubce mohou uvozovky, čárky a složené závorky JSON vážit víc než odsazovací mezery YAML.
7. Tři věci, které se nevrátí
Předchozí části řešily, jak převést správně; tahle řeší, co se při převodu ztratí. Účelově sestavený vstup, naměřeno:
printf '{"order_id":12345678901234567890,"zip":"0150","ts":"2026-11-03","f":1.0e6}' > t.json
yq -o=yaml '.' t.json
yq vydá:
order_id: 1.2345678901234567e+19
zip: "0150"
ts: "2026-11-03"
f: 1000000
A po zpětném převodu do JSON: {"order_id":12345678901234567000,"zip":"0150","ts":"2026-11-03","f":1000000}.
20místné číslo objednávky 12345678901234567890 se změnilo na 12345678901234567000 — poslední tři číslice jsou vynulované a neobjeví se žádné varování. Důvod: yq jde přes parsování float64 v Go; celá čísla větší než 2^53 nelze reprezentovat přesně, proto se výstup přepne do vědeckého zápisu a původní hodnota je v okamžiku zpětného převodu pryč.
Stejný vstup přes PyYAML:
order_id: 12345678901234567890
zip: '0150'
ts: '2026-11-03'
f: 1000000.0
Celá čísla v Pythonu mají libovolnou přesnost, takže všech 20 číslic přežije a při zpětném čtení zůstane 12345678901234567890. Jsou-li v datech čísla objednávek, snowflake ID nebo čísla dokladů, nepoužívejte yq — sáhněte po Pythonu nebo uložte takové pole rovnou jako řetězec. Celý mechanismus této třídy chyb je vysvětlen v proč velká celá čísla v JSON ztrácejí přesnost.
Druhá ztráta je rozdíl mezi float a int: 1.0e6 se ve výstupu yq stane 1000000 (celým číslem), zatímco PyYAML ponechá 1000000.0. Třetí ztráta je pořadí klíčů — PyYAML ve výchozím stavu řadí, yq a js-yaml ve výchozím stavu zachovávají, což je v týmu napříč jazyky skrytý zdroj neshod. Všechny tři spojuje jedno: příkaz skončí s kódem 0, takže ztráty odhalí jen přímé srovnání vstupu a výstupu, nikoli návratový stav.
8. V CI nestačí convert: validace → převod → lint
Považovat převod za jediný krok je nejčastější vada pipeline. Nulový exit kód znamená „vstupní JSON se podařilo naparsovat", ne „výstup je správný" ani „výstup vyhovuje konzumentovi". Správná podoba má tři fáze:
#!/usr/bin/env bash
set -euo pipefail
# 1. Validate the source: syntax layer
jq empty deployment.json
# 2. Convert: name the output format explicitly
yq -o=yaml '.' deployment.json > deployment.yaml
# 3. Validate the artifact: parseable YAML, and lint-clean
yq -o=json -I=0 '.' deployment.yaml > /dev/null
yamllint deployment.yaml
Řádek yq -o=json ... > /dev/null ve třetím kroku vypadá zbytečně, a přitom je nejužitečnější v celém bloku: vynucuje, aby se hotový soubor znovu naparsoval. Chyby odsazení, nevyřešené kotvy a přepsané typy se projeví právě tady. Vyneháte-li ho, problém počká na kubectl apply nebo na start aplikace, kde je cena chyby úplně jinde.
Je-li výsledkem ručně udržovaná konfigurace (Helm values, docker-compose, workflow GitHub Actions), ověřte navíc tři věci: zda se nepromíchalo pořadí klíčů, zda dlouhá čísla neztratila číslice a zda je potřeba ručně doplnit komentáře. JSON komentáře nemá, takže veškeré poznámky musí vzniknout až po převodu; žádný konvertor je za vás nevymyslí. Osvědčuje se držet vedle generovaného souboru krátkou poznámku o tom, která pole vyžadují ruční kontrolu, aby další člen týmu nepovažoval automaticky vyrobený YAML za finální zdroj pravdy.
Časté dotazy
Mám psát yq -P, nebo -o=yaml?
-o=yaml rozhoduje, zda výstup je YAML; -P rozhoduje, jak YAML vypadá. U vstupního souboru .json je -o=yaml povinné a -P potřebujete jen tehdy, chcete-li vynutit blokový styl nebo odsazení sekvencí. Při čtení ze stdin bez přípony už yq YAML vydává samo, takže stačí samotné -P.
Můžu převedený YAML rovnou podat Kubernetes?
Syntakticky ano, ale se třemi podmínkami: klíče s daným pořadím jako apiVersion nebyly přeřazeny (PyYAML řadí ve výchozím stavu, nastavte sort_keys=False), nevznikly kotvy ani aliasy (js-yaml: nastavte noRefs: true) a dlouhá čísla si ponechala všechny číslice. U manifestů s více zdroji je navíc rozdělte na dokumenty oddělené --- pomocí --split-exp.
Proč je výstup jeden řádek {a: 1, b: 2} místo odsazení?
Je to flow style. Je to platný YAML, ale popírá smysl převodu. Použijte -P pro vynucení blokového stylu; u PyYAML ověřte default_flow_style=False (od PyYAML 5.1 je výchozí hodnota False, starší verze ji potřebují uvést výslovně).
U 20místného čísla objednávky se vynulovaly poslední číslice. Jdou obnovit?
Ne, lze jim jen předejít. yq parsuje přes float64, takže celá čísla nad 2^53 ztrácejí přesnost už z podstaty. Buď přejděte na Python (celá čísla s libovolnou přesností, ověřeno beze ztrát), nebo uložte dlouhá čísla jako řetězce už v JSON — druhá možnost je jistější, protože zároveň řeší stejný problém JSON.parse na straně JavaScriptu.
Online konvertor, nebo příkazová řádka?
U konfigurací obsahujících tajné údaje a snímků API s tokeny používejte lokální příkazovou řádku nebo čistě klientský nástroj a nic nenahrávejte. Pro jednorázové malé dávky, které chcete vizuálně zkontrolovat, je online nástroj rychlejší; stránka /convert/yaml na tomto webu běží celá v prohlížeči a data neopustí váš stroj. Pro všechno dávkové, reprodukovatelné a napojené na CI používejte příkazovou řádku a zapište kontrolní kroky do stejného skriptu.