Da JSON a YAML sulla riga di comando: le quattro trappole dietro un comando yq
In sintesi: Il comando più ricopiato delle guide, yq -P input.json > output.yaml, produce ancora JSON su yq v4. Questo articolo misura yq v4.44.6, PyYAML 6.0.3 e js-yaml su un campione da 2.39 MB, spiega perché l'estensione dell'input decide il formato di output e mostra le scritture corrette per conversione batch, virgolette, indentazione e precisione dei grandi interi.

In sintesi: yq -P input.json > output.yaml, il comando più ricopiato delle guide, produce ancora JSON su yq v4. Per ottenere YAML bisogna scrivere esplicitamente -o=yaml (oppure -oy); -P è soltanto l'abbreviazione di --prettyPrint: gestisce l'indentazione e lo stile a blocchi, non il formato di output.
Convertire JSON in YAML non è mai stato semplice come «cambiare il suffisso del file». Il costo vero si nasconde in quattro punti: il formato di output è deciso dall'estensione dell'input, le regole delle virgolette riscrivono in silenzio le tue stringhe, l'indentazione delle sequenze influenza gli strumenti a valle e un intero grande che passa da yq perde precisione in modo permanente.
1. Eseguiamo prima il comando più diffuso
Su una macchina pulita con yq v4.44.6 installato, crea un input minimale:
printf '{"a":1,"b":{"c":2}}' > s.json
yq -P '.' s.json
Quello che torna non è YAML, ma JSON indentato:
{
"a": 1,
"b": {
"c": 2
}
}
Se segui la guida e redirigi questo output verso output.yaml, ottieni un file con suffisso .yaml e contenuto JSON. Viene letto senza problemi da tutti i parser YAML 1.2 (in YAML 1.2, JSON è un sottoinsieme; questa relazione di contenimento è dimostrata nel dettaglio in i confini della conversione JSON ↔ YAML e XML), quindi a breve termine non si rompe nulla — fino al giorno in cui qualcuno aggiunge un commento o un anchor, o finché il yamllint nella CI non comincia a sputare una sfilza di truthy value should be one of.
La scrittura che produce davvero YAML è quella che indica il formato di output:
yq -o=yaml '.' s.json
# a: 1
# b:
# c: 2
2. Perché: l'estensione decide il formato di output
Il comportamento di yq v4 è «il formato di input decide il formato di output». Desume il formato dall'estensione del file di input e, senza -o, riutilizza quella deduzione. Input .json → output JSON; input .yaml → output YAML; leggendo da stdin senza estensione, il default è YAML.
Lo stesso input, quattro scritture, misurate:
| Comando | Provenienza dell'input | Formato realmente prodotto | Verdetto |
|---|---|---|---|
yq -P '.' s.json |
file s.json |
JSON (indentato) | trappola se finisce in .yaml |
yq -o=yaml '.' s.json |
file s.json |
YAML (stile a blocchi) | corretto |
yq -P -o=yaml '.' s.json |
file s.json |
YAML (stile a blocchi) | corretto, -P superfluo qui |
cat s.json | yq -P '.' |
stdin, senza estensione | YAML (stile a blocchi) | corretto, perché il default è yaml |
La terza riga dice una cosa: -P non è sempre necessario una volta che -o=yaml è presente, ma non per questo è innocuo — vedi i numeri della sezione 6. La quarta spiega perché «ho copiato la guida e funziona»: in molti alimentano yq tramite pipe, aggirando proprio quel ramo dell'estensione.
3. Conversione batch: tre scritture shell
Convertire un singolo file è solo il punto di partenza; gli scenari che richiedono davvero la riga di comando sono quasi tutti batch: trasformare in una volta tutte le configurazioni di una directory, tutti gli snapshot di API catturati.
Ciclo bash / zsh (il più leggibile, per una sola directory):
for f in configs/*.json; do
yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done
find ricorsivo (quando contano anche le sottodirectory):
find . -name '*.json' -exec sh -c 'yq -o=yaml "." "$1" > "${1%.json}.yaml"' _ {} \;
PowerShell (su CI Windows):
Get-ChildItem *.json | ForEach-Object { yq -o=yaml '.' $_.FullName > ($_.BaseName + '.yaml') }
Le tre scritture hanno un prerequisito rigido in comune: verifica prima che il JSON sorgente sia valido. Un convertitore non è un parser: di fronte a un errore di sintassi, molti convertitori si limitano a una posizione vaga invece di elencare i numeri di riga come fa un validatore. Inserire un controllo preventivo nel ciclo non costa quasi nulla:
for f in configs/*.json; do
jq empty "$f" || { echo "invalid: $f"; continue; }
yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done
jq empty si limita ad analizzare senza stampare nulla e su JSON non valido restituisce un codice di uscita diverso da zero; combinato con set -euo pipefail, fa fermare l'intera pipeline al primo file difettoso invece di produrre una pila di output convertiti a metà.
4. Quattro parametri di output, quattro compiti distinti
La lista dei parametri di yq è lunga, ma sulla strada da JSON a YAML ce ne sono solo quattro da ricordare:
| Parametro | Cosa fa | Quando va scritto |
|---|---|---|
-o=yaml / -oy |
imposta il formato di output su YAML | ogni volta che l'input è un file .json |
-P / --prettyPrint |
forza lo stile a blocchi e normalizza l'indentazione | leggendo da stdin, o quando conta l'indentazione delle sequenze |
-I=n |
numero di spazi di indentazione, default 2 | quando a valle servono 4 spazi (alcune guide di stile Ansible) |
--split-exp |
divide gli elementi di un array in più documenti | generando manifesti Kubernetes multi-risorsa |
Il caso dei documenti multipli merita un paragrafo a parte. I manifest Kubernetes separano per convenzione più risorse con ---. Per generarli da un array JSON:
yq -P -o=yaml '.[] | splitDoc' --split-exp 'true' kubernetes-list.json
Senza --split-exp ottieni un'unica sequenza YAML; con l'opzione, ogni elemento dell'array diventa un documento indipendente separato da ---. kubectl apply accetta entrambi, ma la leggibilità del diff è di un altro ordine di grandezza.
5. Virgolette: sei forme di stringa che vanno messe tra virgolette
L'inferenza dei tipi di YAML è implicita: una stringa che somiglia a un numero o a un booleano viene analizzata come tale se non è tra virgolette. Non è un rischio teorico — misurati su yq v4.44.6, i sei valori seguenti ricevono le virgolette automaticamente, il che dimostra che il parser li leggerebbe davvero in modo sbagliato:
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"
Le conseguenze, una per una:
| Valore originale (stringa JSON) | Letto come, senza virgolette | Impatto reale |
|---|---|---|
"1.0" |
float 1.0 |
il numero di versione diventa decimale; 1.10 diventa 1.1 |
"true" |
booleano true |
la logica condizionale si inverte o i tipi non corrispondono |
"8080" |
intero 8080 |
la porta cambia tipo, il confronto tra stringhe fallisce |
"" |
null |
la stringa vuota sparisce, a valle arriva None |
"host: value" |
errore di parsing o mapping annidato | dopo i due punti deve esserci uno spazio |
"2026-11-03" |
oggetto data | deserializzato in datetime.date, non in una stringa |
L'ultima è la più subdola. In JSON una data è soltanto una stringa; dopo la conversione diventa un tipo data di YAML, e al ritorno molte implementazioni emettono un oggetto data in formato ISO invece della stringa originale: la coerenza del round-trip si rompe. Sia PyYAML sia yq scelgono di aggiungere le virgolette su questi valori, il che equivale ad ammettere che il rischio è reale.
Nota anche che la riga unicode: 端口 8080 non è racchiusa tra virgolette: il cinese è sicuro in YAML. Ciò che deve preoccuparti non è quello, ma l'ordine delle chiavi e la precisione, argomento della prossima sezione.
6. Equivalenti in Python e Node, con tempi misurati
yq non si può installare dappertutto: ambienti offline, immagini CI bloccate, Windows senza gestore di pacchetti. Gli equivalenti in Python e Node esistono, ciascuno con i propri default pronti a mordere. Per scegliere tra le tre strade in base allo scenario, scelta degli strumenti JSON a riga di comando e degli editor offre una tabella comparativa completa; questo articolo resta sulla sola direzione JSON → YAML.
Python + PyYAML (tre argomenti, nessuno opzionale):
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, # stile a blocchi, non {a: 1, b: 2}
allow_unicode=True, # il cinese non viene escapato in \u7AEF\u53E3
sort_keys=False, # mantiene l'ordine delle chiavi del JSON
)
Questi tre valgono di default False / False / True, cioè il PyYAML appena installato riordina le chiavi in ordine alfabetico e trasforma il cinese in \uXXXX. Sullo stesso input, con i default, l'output misurato è:
apiVersion: v1
data:
LOG_LEVEL: info
colon: 'host: value'
unicode: "\u7AEF\u53E3 8080"
kind: ConfigMap
metadata:
labels:
app: web
name: app-config
Nota che kind finisce dopo apiVersion e prima di metadata. Nei manifest Kubernetes è convenzione della comunità che apiVersion preceda kind; un ordine delle chiavi scombussolato non rompe il parsing, ma rende la revisione penosa.
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 disattiva l'a capo delle righe lunghe (altrimenti un URL lungo viene spezzato a metà) e noRefs: true vieta la generazione di anchor e alias — obbligatorio quando il consumatore è kubectl o un altro strumento che non li risolve.
Tempi misurati (campione: JSON da 2.39 MB, 12000 record, ciascuno con un oggetto annidato e due array; macchina: l'ambiente sandbox usato per questa attività):
| Implementazione | Conversione | Parsing inverso | Round-trip |
|---|---|---|---|
yq -o=yaml v4.44.6 |
0.45 s | 0.45 s | identico |
yq -P -o=yaml v4.44.6 |
4.16 s | — | identico |
| Node js-yaml 4.x | 0.23 s | 0.11 s | identico |
| Python PyYAML 6.0.3 | 3.26 s | 8.01 s | identico |
Due risultati controintuitivi. Primo, aggiungere -P rende yq circa 9 volte più lento (0.45 s → 4.16 s), pur producendo un output identico byte per byte su questo campione (2,326,895 byte in entrambi i casi) — -P esegue una passata completa di rimpaginazione, un costo tutt'altro che trascurabile che conviene risparmiare quando si convertono centinaia di file. Secondo, il parsing inverso di PyYAML (8.01 s) è più del doppio più lento della conversione diretta (3.26 s): se la tua pipeline deve rileggere YAML più volte, Python non è una buona scelta.
Un'ultima misura: 2.39 MB di JSON diventano 2.33 MB di YAML in stile a blocchi, circa il 3% in meno. La credenza popolare secondo cui «YAML è sempre più grande di JSON» non regge: con annidamenti profondi, virgolette, virgole e parentesi graffe di JSON possono pesare più degli spazi di indentazione di YAML.
7. Tre cose che non tornano indietro
Le sezioni precedenti parlavano di «come convertire correttamente»; questa parla di «cosa si perde strada facendo». Misuriamo su un input costruito apposta:
printf '{"order_id":12345678901234567890,"zip":"0150","ts":"2026-11-03","f":1.0e6}' > t.json
yq -o=yaml '.' t.json
L'output di yq:
order_id: 1.2345678901234567e+19
zip: "0150"
ts: "2026-11-03"
f: 1000000
E il ritorno in JSON: {"order_id":12345678901234567000,"zip":"0150","ts":"2026-11-03","f":1000000}.
Il numero d'ordine a 20 cifre 12345678901234567890 è diventato 12345678901234567000: le ultime tre cifre azzerate, senza il minimo avviso. Il motivo è che yq passa dal percorso di parsing float64 di Go; gli interi oltre 2^53 non hanno rappresentazione esatta, quindi vengono emessi in notazione scientifica e, al ritorno, il valore originale non è più recuperabile.
Lo stesso input affidato a PyYAML:
order_id: 12345678901234567890
zip: '0150'
ts: '2026-11-03'
f: 1000000.0
Gli interi di Python sono a precisione arbitraria: tutte le 20 cifre si conservano e la rilettura restituisce 12345678901234567890. Se i tuoi dati contengono numeri d'ordine, ID snowflake o numeri di documento di questo tipo, non usare yq: passa da Python, oppure trasforma prima il campo in stringa. Il meccanismo completo dietro questa classe di problemi di precisione è spiegato in perché i grandi interi perdono precisione in JSON.
La seconda cosa persa è la distinzione tra float e int: 1.0e6 diventa 1000000 (intero) nell'output di yq, mentre PyYAML conserva 1000000.0. La terza è l'ordine delle chiavi — PyYAML riordina per default, yq e js-yaml conservano per default, e questo è una fonte nascosta di disaccordo nei team che mescolano linguaggi.
8. Nella CI non limitarti a convertire: valida, convertisci, lint
Trattare la conversione come l'unico passaggio è il difetto di pipeline più comune. Un codice di uscita zero dopo la conversione significa soltanto «il JSON di input è stato analizzato», non «l'output è corretto» né «l'output soddisfa il consumatore». La forma corretta è in tre tempi:
#!/usr/bin/env bash
set -euo pipefail
# 1. Valida la sorgente: livello sintattico
jq empty deployment.json
# 2. Converti: indica esplicitamente il formato di output
yq -o=yaml '.' deployment.json > deployment.yaml
# 3. Valida il prodotto: leggibile da un parser YAML e pulito per il lint
yq -o=json -I=0 '.' deployment.yaml > /dev/null
yamllint deployment.yaml
Quel yq -o=json ... > /dev/null al passo 3 sembra superfluo ed è in realtà la riga più utile del blocco: obbliga il prodotto a essere rianalizzato. Errori di indentazione, risoluzione degli anchor fallita, tipi riscritti: tutto emerge proprio lì. Salta quella riga e il problema aspettarà kubectl apply o l'avvio dell'applicazione per esplodere.
Se il prodotto è una configurazione destinata a essere mantenuta da esseri umani (values di Helm, docker-compose, workflow di GitHub Actions), controlla altre tre cose: l'ordine delle chiavi non è stato scombussolato, i numeri lunghi non hanno perso cifre e i commenti vanno ancora aggiunti a mano — JSON non ha commenti, quindi ogni annotazione può essere scritta solo dopo la conversione; nessun convertitore li inventerà al posto tuo.
FAQ
yq -P o -o=yaml: quale devo scrivere?
-o=yaml decide se l'output è YAML; -P decide come appare questo YAML. Con un input .json, -o=yaml è obbligatorio, e -P si aggiunge solo quando vuoi forzare lo stile a blocchi o l'indentazione delle sequenze. Leggendo da stdin senza estensione, yq emette già YAML per default, quindi -P da solo basta.
Posso dare il YAML convertito direttamente a Kubernetes?
Sintatticamente sì, con tre avvertenze: le chiavi con convenzioni d'ordine come apiVersion non sono state riordinate (PyYAML riordina per default, serve sort_keys=False), non sono stati generati anchor o alias (js-yaml: serve noRefs: true) e i numeri lunghi hanno conservato le loro cifre. Per i manifest multi-risorsa, dividi il tutto in documenti separati da --- con --split-exp.
Perché il mio output è su una riga sola, {a: 1, b: 2}, invece di essere indentato?
È lo stile flow. È YAML valido, ma è come non aver convertito. Usa -P per forzare lo stile a blocchi; con PyYAML, verifica default_flow_style=False (è il default da PyYAML 5.1, le versioni più vecchie devono scriverlo esplicitamente).
Il mio numero d'ordine a 20 cifre ha perso le ultime cifre. Come le recupero?
Non si recuperano, si prevengono. yq analizza via float64, quindi gli interi oltre 2^53 perdono precisione per costruzione. O passi a Python (interi a precisione arbitraria, conservazione integrale verificata) o memorizzi i numeri lunghi come stringhe già nel JSON — quest'ultima è la più sicura, perché risolve insieme l'equivalente problema di JSON.parse sul lato JavaScript.
Strumento online o riga di comando?
Per configurazioni che contengono segreti e snapshot di API con token, resta sulla riga di comando locale o su uno strumento puramente frontend, non fare upload. Per qualcosa di una volta sola, di piccolo volume, in cui vuoi una conferma visiva, uno strumento online è più rapido; la pagina /convert/yaml di questo sito gira interamente nel browser e i dati non lasciano la tua macchina. Per tutto ciò che è batch, riproducibile o destinato alla CI, si usa la riga di comando, con i passaggi di validazione scritti nello stesso script.