I confini della conversione JSON ↔ YAML e XML
In sintesi: L'affermazione che JSON è un sottoinsieme di YAML garantisce una sola direzione, e XML non ha alcuna mappatura JSON ufficiale — tutto si basa sulla convenzione.

In sintesi: usa JSON come formato intermedio: analizza la sorgente in un modello di dati, poi serializza verso la destinazione; prima di convertire, decidi se commenti, attributi e namespace XML e tipi numerici o booleani devono sopravvivere.
Se una pipeline di conversione è utilizzabile non dipende da quanto è bello l'output, ma da quanto sei consapevole di ciò che ha buttato via.
1. JSON e YAML: contenimento unidirezionale
La specifica YAML 1.2 lo dice chiaramente: JSON è un sottoinsieme di YAML. Cioè, qualsiasi JSON valido è YAML valido:
{"name": "json", "tags": ["a", "b"], "count": 3}
Questo frammento è sia JSON sia YAML in stile flow. Il contrario non vale.
| Funzionalità | JSON | YAML 1.2 |
|---|---|---|
| Commenti | ✗ | ✓ |
| Anchor e alias | ✗ | ✓ |
Documenti multipli (---) |
✗ | ✓ |
| Chiavi non stringa | ✗ | ✓ (1: a, true: b) |
| Chiavi complesse | ✗ | ✓ (? [a, b]) |
| Sensibile all'indentazione | ✗ | ✓ (l'indentazione con tab viene rifiutata) |
| Inferenza dei tipi | esplicita | implicita (0o17, 0x1F, .inf) |
| Leggibilità su una riga | buona | scarsa (i contenuti lunghi si affidano alla sintassi di folding) |
1.1 L'inferenza dei tipi è la trappola più grande
YAML interpreta alcuni valori che sembrano stringhe come altri tipi:
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'esempio di NO ha bruciato parecchia gente: con un parser YAML 1.1 (alcune librerie più vecchie), country: NO diventa false.
Contromisura: metti tra virgolette ogni identificatore che "sembra un numero o un booleano", come in version: "1.10".
1.2 Anchor, alias e documenti multipli
defaults: &defaults
retries: 3
timeout: 30
serviceA:
<<: *defaults
host: a.example.com
---
serviceB:
<<: *defaults
host: b.example.com
Dopo aver espanso gli anchor e aver convertito in JSON si ottengono di solito due oggetti di configurazione duplicati, oppure un singolo array (i documenti multipli devono diventare [{...}, {...}]).
In ogni caso, gli anchor non vengono ripristinati nella conversione inversa — quella perdita è inevitabile.
1.3 Conversione che preserva i commenti
La maggior parte delle librerie elimina semplicemente i commenti durante la conversione. Quando devi conservarli, usa l'API Document del pacchetto yaml:
import { parseDocument } from 'yaml';
const document = parseDocument(`
# Service configuration
port: 8080
`);
document.set('port', 3000);
console.log(document.toString());
// # Service configuration
// port: 3000
Il test è semplice: se questo file verrà modificato dalle persone più e più volte, non prendere la strada "parse → object → serialize".
1.4 Come verificare la coerenza del round-trip
/**
* 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);
}
Eseguirlo su dati reali vale più che leggere la documentazione dieci volte. Assicurati che il tuo set di esempi copra: null, la stringa vuota, l'array vuoto, "00", "1e5", lo zero negativo, gli interi più grandi di 2^53 e le stringhe lunghe che contengono a capo.
2. JSON e XML: non esiste una mappatura standard
XML precede JSON di molto e ha capacità strutturali molto più forti (attributi, namespace, contenuto misto, DTD). Tra i due non esiste una specifica di mappatura ufficiale; ogni strumento fa la stessa cosa: sceglie una convenzione, poi cerca di non perdere troppo.
2.1 Convenzioni comuni
| Convenzione | Rappresentazione | Esempio |
|---|---|---|
| Elemento → chiave dell'oggetto | Un elemento con lo stesso nome diventa un campo | <a>1</a> → {"a": "1"} |
Attributo → prefisso @ |
@name |
<a id="1"/> → {"a": {"@id": "1"}} |
Testo → #text |
Quando ci sono sia un attributo sia del testo | <a id="1">hi</a> → {"a": {"@id": "1", "#text": "hi"}} |
| Elemento ripetuto → array | Due o più occorrenze diventano un array | <i>1</i><i>2</i> → {"i": ["1","2"]} |
| Namespace → prefisso | 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 Cinque perdite inevitabili
- Ordine degli elementi: gli oggetti JSON non sono ordinati, mentre XML è una sequenza ordinata di elementi;
- Attributi vs elementi figli: in XML hanno semantica diversa, ma in JSON diventano entrambi campi semplici;
- Contenuto misto: strutture come
<p>你好 <b>世界</b> 再见</p>non possono essere espresse con un oggetto semplice; - Dichiarazioni di namespace:
xmlnsè un concetto di scope; una volta appiattito in campi, lo scope è perso; - CDATA ed entità:
<![CDATA[...]]>e&in JSON non sono altro che testo.
2.3 Il dilemma di "un elemento o un array"
<list><item>1</item></list>
Con un singolo item, xml2js restituisce un oggetto per impostazione predefinita e restituisce un array solo quando ce ne sono due.
Di conseguenza il codice frontend deve scrivere Array.isArray(x) ? x : [x].
Contromisura: attiva l'opzione "sempre array" durante il parsing, oppure dichiara il campo come tipo array in base a uno schema.
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 Promemoria di sicurezza
Prima di analizzare XML non fidato devi disattivare le entità esterne, altrimenti puoi innescare XXE (lettura di file locali, sondaggio di reti interne):
// In the Node ecosystem, DTDs and external entities are usually disabled through parser options
const parserOptions = { doctype: false, externalEntities: false };
XML → JSON è solo il primo strato di conversione; convertire non elimina la superficie d'attacco che XML porta con sé.
3. Dove si colloca ciascuno dei tre formati
| Scenario | Formato consigliato | Motivo |
|---|---|---|
| API HTTP pubbliche | JSON | Supportato nativamente dai browser e da ogni linguaggio |
| Configurazione mantenuta a mano | YAML | Commenti, indentazione, leggibilità |
| Documenti che richiedono uno schema forte e namespace (SOAP, Office Open XML, SVG) | XML | Capacità strutturali e l'ecosistema esistente |
| Interazione con strumenti operativi (K8s, CI) | YAML | Il valore predefinito dell'ecosistema |
| Persistenza, cache, log | JSON / JSONL | Universale e senza dipendenze |
4. Note di implementazione
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',
});
}
Alcune opzioni pratiche:
lineWidth: 0disattiva l'a capo automatico; altrimenti gli URL lunghi vengono spezzati su più righe e diventano difficili da leggere;- racchiudere tutto tra virgolette doppie evita che valori come
NOe1.10vengano fraintesi; - per i dati con precisione numerica sensibile, converti in stringhe prima di serializzare.
Per la direzione XML, fissa un'unica funzione di mappatura e metti "prefisso degli attributi", "nome della chiave di testo" e "strategia per gli array" nella sua firma invece di spargerli in giro:
type XmlMappingOptions = {
attributePrefix: string;
textKey: string;
alwaysArray: boolean;
};
const defaultMapping: XmlMappingOptions = {
attributePrefix: '@',
textKey: '#text',
alwaysArray: false,
};
5. Checklist
- [ ] Ogni identificatore YAML che "sembra un numero o un booleano" è tra virgolette;
- [ ] È definito un set di esempi di validazione per JSON → YAML → JSON, coperto dai test;
- [ ] Quando i commenti devono essere preservati, si usa l'API Document invece di un round trip sugli oggetti;
- [ ] La convenzione di mappatura XML è messa per iscritto e il team condivide un unico set di parametri;
- [ ] DTD ed entità esterne sono disattivati durante il parsing di XML non fidato;
- [ ] Si confrontano conteggi di byte e di campi prima e dopo la conversione, e ogni anomalia genera un avviso.
FAQ
Come scelgo la direzione di conversione tra JSON, YAML e XML?
Preferisci JSON come formato intermedio: analizza prima YAML e XML in un modello di dati, poi serializza da quel modello invece di riscrivere il testo. Usa l'API Document di YAML quando i commenti devono sopravvivere, e fissa una convenzione di mappatura XML prima di aver bisogno di attributi o namespace.
Perché la conversione da XML a JSON perde attributi e namespace?
JSON non ha il concetto di attributi o namespace, quindi gli attributi XML, i prefissi e gli URI di namespace devono essere ripiegati in chiavi ordinarie, di solito con un prefisso @ o un nodo #text. Una volta che le regole di mappatura divergono, lo stesso XML produce JSON diversi e non può fare round-trip.
Devo mantenere JSON, YAML e XML sincronizzati a due vie nel tempo?
No. Commenti, anchor, attributi XML e namespace sono soggetti a perdita tra i formati, e la sincronizzazione a due vie accumula continuamente differenze. Scegli un formato sorgente e tratta gli altri come esportazioni una tantum o viste temporanee.