JSON vers YAML en ligne de commande : les quatre pièges derrière une commande yq
L’essentiel : La commande la plus recopiée des tutoriels, yq -P input.json > output.yaml, produit encore du JSON. Cet article teste yq v4.44.6, PyYAML 6.0.3 et js-yaml sur un échantillon de 2.39 MB, explique pourquoi l'extension de l'entrée décide du format de sortie, et donne les écritures correctes pour la conversion par lots, les guillemets, l'indentation et la précision des grands entiers.

En bref : yq -P input.json > output.yaml, la commande la plus recopiée des tutoriels, produit encore du JSON sur yq v4. Pour obtenir du YAML il faut écrire explicitement -o=yaml (ou -oy) ; -P n'est que l'abréviation de --prettyPrint, il gère l'indentation et le style bloc, pas le format de sortie.
Convertir du JSON en YAML n'a jamais consisté à « changer le suffixe du fichier ». Le vrai prix se cache à quatre endroits : le format de sortie est décidé par l'extension de l'entrée, les règles de citation réécrivent vos chaînes en silence, l'indentation des séquences perturbe les outils en aval, et un grand entier qui traverse yq perd sa précision de façon permanente.
1. Exécutons d'abord la commande la plus populaire
Sur une machine propre où yq v4.44.6 est installé, créez une entrée minimale :
printf '{"a":1,"b":{"c":2}}' > s.json
yq -P '.' s.json
Ce qui revient n'est pas du YAML, c'est du JSON indenté :
{
"a": 1,
"b": {
"c": 2
}
}
Si vous suivez le tutoriel et redirigez cette sortie vers output.yaml, vous obtenez un fichier dont le suffixe est .yaml et dont le contenu est du JSON. Tous les analyseurs YAML 1.2 le lisent sans broncher (dans YAML 1.2, JSON est un sous-ensemble ; cette relation d'inclusion est démontrée en détail dans les frontières de la conversion entre JSON, YAML et XML), donc rien ne casse à court terme — jusqu'au jour où quelqu'un y ajoute un commentaire ou une ancre, ou jusqu'à ce que le yamllint de la CI crache une longue série de truthy value should be one of.
L'écriture qui produit réellement du YAML est celle qui nomme le format de sortie :
yq -o=yaml '.' s.json
# a: 1
# b:
# c: 2
2. Pourquoi : l'extension décide du format de sortie
Le comportement de yq v4, c'est « le format d'entrée décide du format de sortie ». Il déduit le format de l'extension du fichier d'entrée et, en l'absence de -o, réutilise cette déduction. Une entrée .json donne du JSON en sortie ; une entrée .yaml donne du YAML ; une lecture depuis stdin sans extension passe par défaut en YAML.
Quatre écritures sur la même entrée, mesurées :
| Commande | Source de l'entrée | Format réellement produit | Verdict |
|---|---|---|---|
yq -P '.' s.json |
fichier s.json |
JSON (indenté) | piège si redirigé vers .yaml |
yq -o=yaml '.' s.json |
fichier s.json |
YAML (style bloc) | correct |
yq -P -o=yaml '.' s.json |
fichier s.json |
YAML (style bloc) | correct, -P superflu ici |
cat s.json | yq -P '.' |
stdin, sans extension | YAML (style bloc) | correct, car YAML par défaut |
La troisième ligne dit une chose : -P n'est pas toujours nécessaire une fois que -o=yaml est présent, mais il n'est pas inoffensif pour autant — voyez les chiffres de la section 6. La quatrième explique pourquoi « j'ai recopié le tutoriel et ça marche » : beaucoup de gens alimentent yq par un pipe, ce qui contourne justement la branche de l'extension.
3. Conversion par lots : trois écritures shell
Convertir un seul fichier n'est qu'un point de départ ; les scénarios qui demandent vraiment la ligne de commande sont presque tous massifs : transformer d'un coup toutes les configurations d'un répertoire, tous les instantanés d'API capturés.
Boucle bash / zsh (la plus lisible, pour un seul répertoire) :
for f in configs/*.json; do
yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done
find récursif (quand les sous-répertoires comptent aussi) :
find . -name '*.json' -exec sh -c 'yq -o=yaml "." "$1" > "${1%.json}.yaml"' _ {} \;
PowerShell (sur une CI Windows) :
Get-ChildItem *.json | ForEach-Object { yq -o=yaml '.' $_.FullName > ($_.BaseName + '.yaml') }
Les trois écritures partagent un prérequis absolu : vérifiez d'abord que le JSON source est valide. Un convertisseur n'est pas un analyseur : face à une erreur de syntaxe, beaucoup de convertisseurs se contentent d'une position vague au lieu de détailler les numéros de ligne comme le fait un validateur. Ajouter une vérification en amont dans la boucle ne coûte presque rien :
for f in configs/*.json; do
jq empty "$f" || { echo "invalid: $f"; continue; }
yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done
jq empty se contente d'analyser sans rien afficher et renvoie un code de sortie non nul sur du JSON invalide ; combiné à set -euo pipefail, il arrête toute la chaîne au premier fichier défectueux au lieu de produire un tas de sorties à moitié converties.
4. Quatre paramètres de sortie, quatre rôles distincts
La liste des paramètres de yq est longue, mais sur le chemin JSON vers YAML il n'y en a que quatre à retenir :
| Paramètre | Rôle | Quand il faut l'écrire |
|---|---|---|
-o=yaml / -oy |
fixe le format de sortie à YAML | dès que l'entrée est un fichier .json |
-P / --prettyPrint |
force le style bloc et normalise l'indentation | lecture depuis stdin, ou indentation de séquence imposée |
-I=n |
nombre d'espaces d'indentation, 2 par défaut | l'aval exige 4 espaces (certains guides de style Ansible) |
--split-exp |
éclate les éléments d'un tableau en plusieurs documents | génération de manifestes Kubernetes multi-ressources |
Le cas des documents multiples mérite un paragraphe à part. Les manifestes Kubernetes séparent par convention plusieurs ressources avec ---. Pour les générer depuis un tableau JSON :
yq -P -o=yaml '.[] | splitDoc' --split-exp 'true' kubernetes-list.json
Sans --split-exp, vous obtenez une seule séquence YAML ; avec, chaque élément du tableau devient un document indépendant séparé par ---. kubectl apply avale les deux, mais la lisibilité du diff n'est pas du tout du même ordre.
5. Les guillemets : six formes de chaînes qui doivent être citées
L'inférence de type de YAML est implicite : une chaîne qui ressemble à un nombre ou à un booléen sera analysée comme telle si elle n'est pas entre guillemets. Ce n'est pas un risque théorique — mesurées sur yq v4.44.6, les six valeurs suivantes se voient ajouter des guillemets automatiquement, ce qui prouve que l'analyseur les lirait réellement de travers :
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"
Les conséquences, une par une :
| Valeur d'origine (chaîne JSON) | Lue comme, sans guillemets | Impact réel |
|---|---|---|
"1.0" |
flottant 1.0 |
le numéro de version devient décimal ; 1.10 devient 1.1 |
"true" |
booléen true |
la logique conditionnelle s'inverse ou les types ne correspondent plus |
"8080" |
entier 8080 |
le port change de type, la comparaison de chaînes échoue |
"" |
null |
la chaîne vide disparaît, l'aval reçoit None |
"host: value" |
erreur d'analyse ou mapping imbriqué | un deux-points doit être suivi d'une espace |
"2026-11-03" |
objet date | désérialisé en datetime.date et non en chaîne |
La dernière est la plus vicieuse. En JSON, une date n'est qu'une chaîne ; après conversion elle devient un type date YAML, et au retour beaucoup d'implémentations ressortent un objet date au format ISO plutôt que la chaîne d'origine : la cohérence aller-retour est rompue. PyYAML comme yq choisissent d'ajouter des guillemets sur ces valeurs, ce qui revient à reconnaître que le risque est réel.
Notons au passage que la ligne unicode: 端口 8080 n'est pas mise entre guillemets : le chinois est sûr en YAML. Ce qui doit vous inquiéter n'est pas là, c'est l'ordre des clés et la précision, au programme de la section suivante.
6. Équivalents Python et Node, avec les temps mesurés
yq ne s'installe pas partout : environnements hors ligne, images CI verrouillées, Windows sans gestionnaire de paquets. Les équivalents Python et Node existent, chacun avec ses propres valeurs par défaut prêtes à mordre. Pour choisir entre les trois voies selon le scénario, choix des outils JSON en ligne de commande et des éditeurs propose un tableau comparatif complet ; cet article se limite à la direction JSON → YAML.
Python + PyYAML (trois arguments, aucun n'est optionnel) :
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, # style bloc, pas {a: 1, b: 2}
allow_unicode=True, # le chinois n'est pas échappé en \u7AEF\u53E3
sort_keys=False, # conserve l'ordre des clés du JSON
)
Ces trois arguments valent par défaut False / False / True, autrement dit le PyYAML sorti de la boîte réordonne les clés par ordre alphabétique et transforme le chinois en \uXXXX. Sur la même entrée, avec les valeurs par défaut, la sortie mesurée est :
apiVersion: v1
data:
LOG_LEVEL: info
colon: 'host: value'
unicode: "\u7AEF\u53E3 8080"
kind: ConfigMap
metadata:
labels:
app: web
name: app-config
Remarquez que kind se retrouve après apiVersion et avant metadata. Dans un manifeste Kubernetes, la convention communautaire veut que apiVersion précède kind ; un ordre de clés bouleversé ne casse pas l'analyse, mais il rend la revue pénible.
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 désactive le repli des lignes longues (sinon une URL longue est coupée en son milieu) et noRefs: true interdit la génération d'ancres et d'alias — obligatoire quand le consommateur est kubectl ou un autre outil qui ne les résout pas.
Temps mesurés (échantillon : JSON de 2.39 MB, 12000 enregistrements, chacun contenant un objet imbriqué et deux tableaux ; machine : l'environnement sandbox utilisé pour cette tâche) :
| Implémentation | Conversion | Analyse inverse | Aller-retour |
|---|---|---|---|
yq -o=yaml v4.44.6 |
0.45 s | 0.45 s | identique |
yq -P -o=yaml v4.44.6 |
4.16 s | — | identique |
| Node js-yaml 4.x | 0.23 s | 0.11 s | identique |
| Python PyYAML 6.0.3 | 3.26 s | 8.01 s | identique |
Deux résultats contre-intuitifs. Premièrement, ajouter -P rend yq environ 9 fois plus lent (0.45 s → 4.16 s) alors que la sortie est identique à l'octet près sur cet échantillon (2,326,895 octets dans les deux cas) — -P effectue une passe complète de remise en page, un coût loin d'être négligeable quand on convertit des centaines de fichiers, autant l'économiser. Deuxièmement, l'analyse inverse de PyYAML (8.01 s) est plus de deux fois plus lente que sa conversion directe (3.26 s) : si votre pipeline doit relire le YAML à répétition, Python n'est pas un bon choix.
Une dernière mesure : 2.39 MB de JSON deviennent 2.33 MB de YAML en style bloc, soit environ 3 % de moins. L'idée reçue selon laquelle « YAML est forcément plus gros que JSON » est fausse : les guillemets, virgules et accolades de JSON pèsent parfois plus lourd, en cas d'imbrication profonde, que les espaces d'indentation de YAML.
7. Trois choses qui ne reviennent pas
Les sections précédentes disaient « comment convertir correctement » ; celle-ci dit « ce qui est perdu en route ». Mesurons sur une entrée fabriquée exprès :
printf '{"order_id":12345678901234567890,"zip":"0150","ts":"2026-11-03","f":1.0e6}' > t.json
yq -o=yaml '.' t.json
Sortie de yq :
order_id: 1.2345678901234567e+19
zip: "0150"
ts: "2026-11-03"
f: 1000000
Et le retour en JSON : {"order_id":12345678901234567000,"zip":"0150","ts":"2026-11-03","f":1000000}.
Le numéro de commande à 20 chiffres 12345678901234567890 est devenu 12345678901234567000 : les trois derniers chiffres passent à zéro, sans le moindre avertissement. La cause : yq passe par le chemin de parsing float64 de Go ; au-delà de 2^53, un entier n'a plus de représentation exacte, il est donc sorti en notation scientifique et, au retour, la valeur d'origine n'est plus récupérable.
La même entrée confiée à PyYAML :
order_id: 12345678901234567890
zip: '0150'
ts: '2026-11-03'
f: 1000000.0
Les entiers de Python sont en précision arbitraire : les 20 chiffres sont intégralement conservés et la relecture redonne bien 12345678901234567890. Si vos données contiennent des numéros de commande, des ID snowflake ou des numéros d'identité de ce genre, n'utilisez pas yq : passez par Python, ou transformez d'abord le champ en chaîne. Le mécanisme complet de cette classe de problèmes de précision est détaillé dans pourquoi les grands entiers perdent leur précision en JSON.
La deuxième chose perdue est la distinction entre float et int : 1.0e6 devient 1000000 (entier) dans la sortie de yq, alors que PyYAML conserve 1000000.0. La troisième est l'ordre des clés — PyYAML réordonne par défaut, yq et js-yaml conservent par défaut, ce qui constitue une source de divergence cachée dans les équipes qui mélangent les langages.
8. En CI, ne vous contentez pas de convertir : valider, convertir, linter
Traiter la conversion comme l'unique étape est le défaut de pipeline le plus courant. Un code de sortie nul après conversion signifie seulement « le JSON d'entrée s'est analysé », ni « la sortie est correcte » ni « la sortie satisfait le consommateur ». La bonne forme est en trois temps :
#!/usr/bin/env bash
set -euo pipefail
# 1. Valider la source : couche syntaxique
jq empty deployment.json
# 2. Convertir : nommer explicitement le format de sortie
yq -o=yaml '.' deployment.json > deployment.yaml
# 3. Valider le produit : analysable par un parseur YAML et conforme au lint
yq -o=json -I=0 '.' deployment.yaml > /dev/null
yamllint deployment.yaml
Ce yq -o=json ... > /dev/null de l'étape 3 a l'air superflu et c'est en fait la ligne la plus utile du bloc : il force le produit à être réanalysé. Erreurs d'indentation, échecs de résolution d'ancres, types réécrits : tout remonte à cet endroit. Sautez cette ligne et le problème attendra kubectl apply ou le démarrage de l'application pour exploser.
Si le produit est une configuration destinée à être maintenue par des humains (values Helm, docker-compose, workflows GitHub Actions), vérifiez trois choses de plus : l'ordre des clés n'a pas été bouleversé, les grands nombres n'ont pas perdu de chiffres, et les commentaires restent à ajouter à la main — JSON n'a pas de commentaires, toute annotation ne peut donc être écrite qu'après conversion, aucun convertisseur ne les inventera pour vous.
FAQ
-P ou -o=yaml : lequel faut-il écrire ?
-o=yaml décide si la sortie est du YAML ; -P décide à quoi ce YAML ressemble. Avec une entrée .json, -o=yaml est obligatoire, et -P ne s'ajoute que si vous voulez forcer le style bloc ou l'indentation des séquences. En lecture depuis stdin sans extension, yq sort déjà du YAML par défaut : -P seul suffit alors.
Le YAML converti peut-il être donné directement à Kubernetes ?
Syntaxiquement oui, à trois conditions : les clés soumises à une convention d'ordre comme apiVersion n'ont pas été réordonnées (PyYAML réordonne par défaut, il faut régler sort_keys=False), aucune ancre ni alias n'a été généré (js-yaml : régler noRefs: true), et les grands nombres ont gardé leurs chiffres. Pour un manifeste multi-ressources, découpez encore le tout en documents séparés par --- avec --split-exp.
Pourquoi ma sortie tient sur une ligne, {a: 1, b: 2}, au lieu d'être indentée ?
C'est le style flux. C'est du YAML valide, mais autant ne pas avoir converti. Utilisez -P pour forcer le style bloc ; avec PyYAML, vérifiez default_flow_style=False (la valeur par défaut est False depuis PyYAML 5.1, les versions plus anciennes doivent l'écrire explicitement).
Mon numéro de commande à 20 chiffres a perdu ses derniers chiffres, comment le récupérer ?
On ne le récupère pas, on prévient. yq analyse en float64 : au-delà de 2^53, un entier perd sa précision par construction. Soit vous passez à Python (entiers en précision arbitraire, conservation intégrale vérifiée), soit vous stockez les grands nombres comme chaînes dès le JSON — cette seconde option est la plus sûre, car elle règle en même temps le problème équivalent de JSON.parse côté JavaScript.
Outil en ligne ou ligne de commande ?
Pour une configuration contenant des secrets ou un instantané d'API avec des tokens, restez en ligne de commande locale ou sur un outil purement frontend, n'uploadez rien. Pour un besoin ponctuel, de petit volume, avec confirmation visuelle, un outil en ligne est plus rapide ; la page /convert/yaml de ce site tourne entièrement dans le navigateur et les données ne quittent pas votre machine. Pour tout ce qui est massif, reproductible ou destiné à la CI, c'est la ligne de commande, avec les étapes de validation écrites dans le même script.