Sempadan Penukaran JSON ↔ YAML dan XML
Inti pati: Dakwaan bahawa JSON ialah subset YAML hanya menjamin satu arah, dan XML tidak mempunyai pemetaan JSON rasmi sama sekali — segala-galanya bergantung pada konvensyen.

Kesimpulan utama: gunakan JSON sebagai format perantaraan: hurai sumber menjadi model data, kemudian serialkan kepada sasaran; sebelum menukar, tentukan sama ada komen, atribut XML dan ruang nama, serta jenis berangka atau boolean mesti bertahan.
Sama ada saluran penukaran boleh digunakan tidak bergantung pada betapa cantik outputnya, tetapi pada sama ada anda sedar berapa banyak yang telah dibuangnya.
1. JSON dan YAML: pengandungan satu arah
Spesifikasi YAML 1.2 menyatakannya dengan jelas: JSON ialah subset YAML. Iaitu, mana-mana JSON yang sah ialah YAML yang sah:
{"name": "json", "tags": ["a", "b"], "count": 3}
Serpihan ini ialah JSON dan juga gaya aliran YAML. Sebaliknya tidak benar.
| Ciri | JSON | YAML 1.2 |
|---|---|---|
| Komen | ✗ | ✓ |
| Sauh dan alias | ✗ | ✓ |
Berbilang dokumen (---) |
✗ | ✓ |
| Kunci bukan rentetan | ✗ | ✓ (1: a, true: b) |
| Kunci kompleks | ✗ | ✓ (? [a, b]) |
| Peka inden | ✗ | ✓ (inden tab ditolak) |
| Inferens jenis | eksplisit | tersirat (0o17, 0x1F, .inf) |
| Kebolehbacaan satu baris | baik | lemah (kandungan panjang bergantung pada sintaks lipatan) |
1.1 Inferens jenis ialah perangkap terbesar
YAML menghurai sesetengah nilai yang kelihatan seperti rentetan menjadi jenis lain:
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
Contoh NO telah membakar ramai orang: dengan penghurai YAML 1.1 (sesetengah pustaka lama), country: NO menjadi false.
Langkah balas: petik setiap pengecam yang "kelihatan seperti nombor atau boolean", seperti dalam version: "1.10".
1.2 Sauh, alias, dan berbilang dokumen
defaults: &defaults
retries: 3
timeout: 30
serviceA:
<<: *defaults
host: a.example.com
---
serviceB:
<<: *defaults
host: b.example.com
Selepas mengembangkan sauh dan menukar kepada JSON anda biasanya berakhir dengan dua objek konfigurasi berganda, atau dengan satu tatasusunan (berbilang dokumen mesti menjadi [{...}, {...}]).
Sama ada cara, sauh tidak dipulihkan pada penukaran songsang — kehilangan itu tidak dapat dielakkan.
1.3 Penukaran yang mengekalkan komen
Kebanyakan pustaka hanya menggugurkan komen semasa penukaran. Apabila anda perlu mengekalkannya, gunakan Document API pakej yaml:
import { parseDocument } from 'yaml';
const document = parseDocument(`
# Service configuration
port: 8080
`);
document.set('port', 3000);
console.log(document.toString());
// # Service configuration
// port: 3000
Ujiannya mudah: jika fail ini akan disunting oleh manusia berulang kali, jangan ambil laluan "hurai → objek → serialkan".
1.4 Cara mengesahkan konsistensi perjalanan pergi balik
/**
* 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);
}
Menjalankannya terhadap data sebenar lebih berharga daripada membaca dokumentasi sepuluh kali. Pastikan set sampel anda merangkumi: null, rentetan kosong, tatasusunan kosong, "00", "1e5", sifar negatif, integer lebih besar daripada 2^53, dan rentetan panjang yang mengandungi baris baharu.
2. JSON dan XML: tiada pemetaan standard
XML mendahului JSON dengan jarak yang jauh dan mempunyai keupayaan struktur yang lebih kuat (atribut, ruang nama, kandungan bercampur, DTD). Antara keduanya tiada spesifikasi pemetaan rasmi; setiap alat melakukan perkara yang sama: pilih satu konvensyen, kemudian cuba untuk tidak kehilangan terlalu banyak.
2.1 Konvensyen lazim
| Konvensyen | Perwakilan | Contoh |
|---|---|---|
| Elemen → kunci objek | Elemen bernama sama menjadi medan | <a>1</a> → {"a": "1"} |
Atribut → awalan @ |
@name |
<a id="1"/> → {"a": {"@id": "1"}} |
Teks → #text |
Apabila terdapat atribut dan teks sekali | <a id="1">hi</a> → {"a": {"@id": "1", "#text": "hi"}} |
| Elemen berulang → tatasusunan | Dua atau lebih kejadian menjadi tatasusunan | <i>1</i><i>2</i> → {"i": ["1","2"]} |
| Ruang nama → awalan | 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 Lima kehilangan yang tidak dapat dielakkan
- Susunan elemen: objek JSON tidak tersusun, manakala XML ialah urutan elemen tersusun;
- Atribut berbanding elemen anak: ia membawa semantik berbeza dalam XML, tetapi kedua-duanya menjadi medan biasa dalam JSON;
- Kandungan bercampur: struktur seperti
<p>你好 <b>世界</b> 再见</p>tidak boleh dinyatakan dengan objek biasa; - Pengisytiharan ruang nama:
xmlnsialah konsep skop; sebaik sahaja diratakan menjadi medan, skop itu hilang; - CDATA dan entiti:
<![CDATA[...]]>dan&tidak lebih daripada teks dalam JSON.
2.3 Dilema "satu elemen atau tatasusunan"
<list><item>1</item></list>
Dengan satu item, xml2js memulangkan objek secara lalai dan hanya memulangkan tatasusunan apabila terdapat dua.
Akibatnya kod frontend perlu menulis Array.isArray(x) ? x : [x].
Langkah balas: hidupkan pilihan "sentiasa tatasusunan" semasa menghurai, atau isytiharkan medan itu sebagai jenis tatasusunan berdasarkan skema.
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 Peringatan keselamatan
Sebelum menghurai XML yang tidak dipercayai anda mesti melumpuhkan entiti luaran, jika tidak anda mungkin mencetuskan XXE (membaca fail setempat, menyiasat rangkaian dalaman):
// In the Node ecosystem, DTDs and external entities are usually disabled through parser options
const parserOptions = { doctype: false, externalEntities: false };
XML → JSON hanya lapisan pertama penukaran; menukar tidak menghapuskan permukaan serangan yang dibawa oleh XML.
3. Di mana setiap satu daripada tiga format sesuai
| Senario | Format disyorkan | Sebab |
|---|---|---|
| API HTTP awam | JSON | Disokong secara asli oleh pelayar dan setiap bahasa |
| Konfigurasi yang diselenggara dengan tangan | YAML | Komen, inden, kebolehbacaan |
| Dokumen yang memerlukan skema dan ruang nama yang kuat (SOAP, Office Open XML, SVG) | XML | Keupayaan struktur dan ekosistem sedia ada |
| Interaksi dengan alat operasi (K8s, CI) | YAML | Lalai ekosistem |
| Kegigihan, cache, log | JSON / JSONL | Universal dan tanpa kebergantungan |
4. Nota pelaksanaan
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',
});
}
Beberapa pilihan praktikal:
lineWidth: 0mematikan pembalutan automatik; jika tidak URL panjang dipisahkan merentas baris dan menjadi sukar dibaca;- memetik segala-galanya dengan tanda petikan berganda menghalang nilai seperti
NOdan1.10daripada disalah baca; - bagi data dengan ketepatan berangka sensitif, tukar kepada rentetan sebelum serialkan.
Untuk arah XML, tetapkan satu fungsi pemetaan tunggal dan letakkan "awalan atribut", "nama kunci teks", dan "strategi tatasusunan" ke dalam tandatangannya dan jangan berselerak di merata tempat:
type XmlMappingOptions = {
attributePrefix: string;
textKey: string;
alwaysArray: boolean;
};
const defaultMapping: XmlMappingOptions = {
attributePrefix: '@',
textKey: '#text',
alwaysArray: false,
};
5. Senarai semak
- [ ] Setiap pengecam "seperti nombor/boolean" dalam YAML dipetik;
- [ ] Set sampel pengesahan bagi JSON → YAML → JSON ditakrifkan dan diliputi ujian;
- [ ] Apabila komen mesti dikekalkan, Document API digunakan dan bukan perjalanan pergi balik objek;
- [ ] Konvensyen pemetaan XML ditulis dan pasukan berkongsi satu set parameter;
- [ ] DTD dan entiti luaran dilumpuhkan semasa menghurai XML yang tidak dipercayai;
- [ ] Kiraan bait dan kiraan medan dibandingkan sebelum dan selepas penukaran, dan sebarang anomali menimbulkan amaran.
Soalan Lazim
Bagaimana saya harus memilih arah penukaran antara JSON, YAML dan XML?
Utamakan JSON sebagai format perantaraan: hurai YAML dan XML menjadi model data dahulu, kemudian serialkan daripada model itu dan bukannya menulis semula teks. Gunakan Document API YAML apabila komen mesti bertahan, dan tetapkan satu konvensyen pemetaan XML sebelum anda memerlukan atribut atau ruang nama.
Mengapa penukaran XML kepada JSON kehilangan atribut dan ruang nama?
JSON tidak mempunyai konsep atribut atau ruang nama, jadi atribut XML, awalan, dan URI ruang nama mesti dilipat menjadi kunci biasa, lazimnya dengan awalan @ atau nod #text. Sebaik sahaja peraturan pemetaan berbeza, XML yang sama menghasilkan JSON berbeza dan tidak boleh membuat perjalanan pergi balik.
Patutkah saya mengekalkan JSON, YAML dan XML dalam penyegerakan dua hala jangka panjang?
Tidak. Komen, sauh, atribut XML dan ruang nama adalah berkehilangan merentas format, dan penyegerakan dua hala terus mengumpul perbezaan. Pilih satu format sumber dan layan yang lain sebagai eksport sekali sahaja atau paparan sementara.