Les frontières de la conversion JSON ↔ YAML et XML
L’essentiel : L'affirmation que JSON est un sous-ensemble de YAML ne garantit qu'un sens, et XML n'a aucune correspondance JSON officielle — tout repose sur des conventions.

L'essentiel : utilisez JSON comme format intermédiaire : parsez la source en un modèle de données, puis sérialisez vers la cible ; avant de convertir, décidez si les commentaires, les attributs et espaces de noms XML, et les types numériques ou booléens doivent survivre.
La question de savoir si un pipeline de conversion est utilisable ne dépend pas de la beauté de sa sortie, mais de votre conscience de tout ce qu'il a jeté.
1. JSON et YAML : une inclusion à sens unique
La spécification YAML 1.2 le dit clairement : JSON est un sous-ensemble de YAML. Autrement dit, tout JSON valide est du YAML valide :
{"name": "json", "tags": ["a", "b"], "count": 3}
Ce fragment est à la fois du JSON et du YAML en style flux. L'inverse ne tient pas.
| Fonctionnalité | JSON | YAML 1.2 |
|---|---|---|
| Commentaires | ✗ | ✓ |
| Ancres et alias | ✗ | ✓ |
Documents multiples (---) |
✗ | ✓ |
| Clés non-chaînes | ✗ | ✓ (1: a, true: b) |
| Clés complexes | ✗ | ✓ (? [a, b]) |
| Sensible à l'indentation | ✗ | ✓ (l'indentation par tabulation est rejetée) |
| Inférence de types | explicite | implicite (0o17, 0x1F, .inf) |
| Lisibilité sur une ligne | bonne | mauvaise (le contenu long dépend de la syntaxe de repli) |
1.1 L'inférence de types est le plus grand piège
YAML convertit certaines valeurs qui ressemblent à des chaînes en d'autres types :
zip: 01234 # string or octal integer? the YAML 1.2 core schema treats it as a string
version: 1.10 # 1.1 (a float); the semantics are lost
country: NO # a string in YAML 1.2; boolean false in YAML 1.1
date: 2026-10-30 # some implementations parse it as a date object
L'exemple NO a brûlé beaucoup de monde : avec un parseur YAML 1.1 (certaines bibliothèques anciennes), country: NO devient false.
Contre-mesure : mettez entre guillemets tout identifiant qui « ressemble à un nombre ou à un booléen », comme version: "1.10".
1.2 Ancres, alias et documents multiples
defaults: &defaults
retries: 3
timeout: 30
serviceA:
<<: *defaults
host: a.example.com
---
serviceB:
<<: *defaults
host: b.example.com
Après expansion des ancres et conversion en JSON, on obtient généralement deux objets de configuration dupliqués, ou un tableau (les documents multiples doivent devenir [{...}, {...}]).
Dans les deux cas, les ancres ne sont pas restaurées lors de la conversion inverse — cette perte est inévitable.
1.3 Une conversion qui préserve les commentaires
La plupart des bibliothèques se contentent de supprimer les commentaires pendant la conversion. Quand il faut les conserver, utilisez l'API Document du paquet yaml :
import { parseDocument } from 'yaml';
const document = parseDocument(`
# Service configuration
port: 8080
`);
document.set('port', 3000);
console.log(document.toString());
// # Service configuration
// port: 3000
Le test est simple : si ce fichier sera modifié par des humains encore et encore, ne prenez pas le chemin « parse → objet → sérialisation ».
1.4 Comment vérifier la cohérence aller-retour
/**
* Verifies round-trip consistency for JSON → YAML → JSON.
* @param sourceValue Original JSON value
* @param stringify YAML serialization function
* @param parse YAML parsing function
* @returns Whether the two results are exactly equal
*/
function isRoundTripStable(
sourceValue: unknown,
stringify: (input: unknown) => string,
parse: (input: string) => unknown,
): boolean {
const yamlText = stringify(sourceValue);
const restored = parse(yamlText);
return JSON.stringify(restored) === JSON.stringify(sourceValue);
}
L'exécuter sur des données réelles vaut mieux que lire dix fois la documentation. Assurez-vous que votre jeu d'échantillons couvre : null, la chaîne vide, le tableau vide, "00", "1e5", le zéro négatif, les entiers plus grands que 2^53, et les chaînes longues contenant des retours à la ligne.
2. JSON et XML : il n'existe pas de mapping standard
XML est bien antérieur à JSON et possède des capacités structurelles bien plus fortes (attributs, espaces de noms, contenu mixte, DTD). Entre les deux, il n'existe aucune spécification officielle de mapping ; chaque outil fait la même chose : choisir une convention, puis essayer de ne pas trop perdre.
2.1 Conventions courantes
| Convention | Représentation | Exemple |
|---|---|---|
| Élément → clé d'objet | Un élément de même nom devient un champ | <a>1</a> → {"a": "1"} |
Attribut → préfixe @ |
@name |
<a id="1"/> → {"a": {"@id": "1"}} |
Texte → #text |
Quand il y a à la fois un attribut et du texte | <a id="1">hi</a> → {"a": {"@id": "1", "#text": "hi"}} |
| Élément répété → tableau | Deux occurrences ou plus deviennent un tableau | <i>1</i><i>2</i> → {"i": ["1","2"]} |
| Espace de noms → préfixe | ns:tag |
<ns:tag/> → {"ns:tag": ...} |
<order id="A1001">
<item sku="X1" qty="2">笔记本</item>
<item sku="X2" qty="1">钢笔</item>
</order>
{
"order": {
"@id": "A1001",
"item": [
{ "@sku": "X1", "@qty": "2", "#text": "笔记本" },
{ "@sku": "X2", "@qty": "1", "#text": "钢笔" }
]
}
}
2.2 Cinq pertes inévitables
- Ordre des éléments : les objets JSON sont non ordonnés, tandis qu'XML est une séquence ordonnée d'éléments ;
- Attributs contre éléments enfants : ils portent des sémantiques différentes en XML, mais deviennent de simples champs en JSON ;
- Contenu mixte : des structures comme
<p>你好 <b>世界</b> 再见</p>ne peuvent pas s'exprimer avec un objet simple ; - Déclarations d'espaces de noms :
xmlnsest un concept de portée ; une fois aplati en champs, la portée disparaît ; - CDATA et entités :
<![CDATA[...]]>et&ne sont plus que du texte en JSON.
2.3 Le dilemme « un élément ou un tableau »
<list><item>1</item></list>
Avec un seul item, xml2js renvoie un objet par défaut et ne renvoie un tableau qu'à partir de deux.
Par conséquent, le code frontend doit écrire Array.isArray(x) ? x : [x].
Contre-mesure : activez l'option « toujours tableau » au parsing, ou déclarez le champ comme type tableau à partir d'un schéma.
import { parseStringPromise } from 'xml2js';
const parsed = await parseStringPromise(xmlText, {
explicitArray: true, // return an array even for a single element
trim: true,
explicitRoot: false,
});
2.4 Rappel de sécurité
Avant de parser du XML non fiable, vous devez désactiver les entités externes, sinon vous pouvez déclencher une XXE (lecture de fichiers locaux, sondage de réseaux internes) :
// In the Node ecosystem, DTDs and external entities are usually disabled through parser options
const parserOptions = { doctype: false, externalEntities: false };
XML → JSON n'est que la première couche de conversion ; convertir n'élimine pas la surface d'attaque qu'apporte XML.
3. Où chaque format trouve sa place
| Scénario | Format recommandé | Raison |
|---|---|---|
| API HTTP publiques | JSON | Nativement pris en charge par les navigateurs et tous les langages |
| Configuration maintenue à la main | YAML | Commentaires, indentation, lisibilité |
| Documents nécessitant un schéma fort et des espaces de noms (SOAP, Office Open XML, SVG) | XML | Capacités structurelles et écosystème existant |
| Interaction avec l'outillage d'exploitation (K8s, CI) | YAML | Défaut de l'écosystème |
| Persistance, caches, journaux | JSON / JSONL | Universel et sans dépendance |
4. Notes d'implémentation
import { stringify as toYaml } from 'yaml';
/**
* Converts a JSON value to YAML text while keeping Chinese characters unescaped.
* @param value The JSON value to convert
* @returns The YAML text
*/
function jsonToYaml(value: unknown): string {
return toYaml(value, {
indent: 2,
lineWidth: 0, // disable automatic wrapping so long strings are not split
defaultStringType: 'QUOTE_DOUBLE',
});
}
Quelques options pratiques :
lineWidth: 0désactive le repli automatique ; sinon les URL longues sont coupées sur plusieurs lignes et deviennent difficiles à lire ;- tout mettre entre guillemets doubles empêche des valeurs comme
NOet1.10d'être mal interprétées ; - pour les données à précision numérique sensible, convertissez en chaînes avant de sérialiser.
Pour la direction XML, fixez une seule fonction de mapping et mettez le « préfixe d'attribut », le « nom de clé de texte » et la « stratégie de tableau » dans sa signature au lieu de les disperser :
type XmlMappingOptions = {
attributePrefix: string;
textKey: string;
alwaysArray: boolean;
};
const defaultMapping: XmlMappingOptions = {
attributePrefix: '@',
textKey: '#text',
alwaysArray: false,
};
5. Liste de vérification
- [ ] Chaque identifiant « semblable à un nombre/booléen » en YAML est entre guillemets ;
- [ ] Un jeu d'échantillons de validation pour JSON → YAML → JSON est défini et couvert par des tests ;
- [ ] Quand les commentaires doivent être préservés, l'API Document est utilisée au lieu d'un aller-retour par objet ;
- [ ] La convention de mapping XML est écrite et l'équipe partage un seul jeu de paramètres ;
- [ ] Les DTD et entités externes sont désactivées lors du parsing de XML non fiable ;
- [ ] Les nombres d'octets et de champs sont comparés avant et après conversion, et toute anomalie déclenche une alerte.
FAQ
Comment choisir la direction de conversion entre JSON, YAML et XML ?
Préférez JSON comme format intermédiaire : parsez d'abord YAML et XML en un modèle de données, puis sérialisez depuis ce modèle plutôt que de réécrire du texte. Utilisez l'API Document de YAML quand les commentaires doivent survivre, et fixez une seule convention de mapping XML avant d'avoir besoin d'attributs ou d'espaces de noms.
Pourquoi la conversion XML vers JSON perd-elle les attributs et les espaces de noms ?
JSON n'a aucun concept d'attribut ni d'espace de noms, donc les attributs, préfixes et URI d'espaces de noms XML doivent être repliés dans des clés ordinaires, souvent avec un préfixe @ ou un nœud #text. Une fois que les règles de mapping divergent, le même XML produit un JSON différent et ne peut pas faire d'aller-retour.
Faut-il maintenir JSON, YAML et XML en synchronisation bidirectionnelle à long terme ?
Non. Les commentaires, ancres, attributs XML et espaces de noms sont avec perte entre formats, et la synchronisation bidirectionnelle accumule sans cesse des différences. Choisissez un format source et traitez les autres comme des exports ponctuels ou des vues temporaires.