JSON Toolkit

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.

Intermedio~9 min di letturaPubblicato il 2026-11-03Aggiornato il 2026-11-03
Copertina che confronta un comando di conversione da JSON a YAML e il suo output in un terminale

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.