ขอบเขตของการแปลง JSON ↔ YAML และ XML
สรุปสำคัญ: คำกล่าวที่ว่า JSON เป็นซูเปอร์เซตของ YAML รับประกันได้เพียงทิศทางเดียว และ XML ไม่มีมาตรฐานการแมป JSON อย่างเป็นทางการเลย — ทุกอย่างขึ้นอยู่กับข้อตกลง

สรุปสั้น: ใช้ JSON เป็นรูปแบบกลาง: แยกวิเคราะห์ต้นทางเป็นโมเดลข้อมูลก่อน แล้วจึงทำให้เป็นอนุกรมไปยังเป้าหมาย; ก่อนแปลงต้องตัดสินว่าคอมเมนต์ attribute และ namespace ของ XML และชนิดตัวเลขหรือบูลีนต้องอยู่รอดหรือไม่
ไปป์ไลน์การแปลงจะใช้งานได้หรือไม่ ไม่ได้ขึ้นกับว่าเอาต์พุตสวยแค่ไหน แต่ขึ้นกับว่าคุณรู้ตัวว่ามันทิ้งอะไรไปมากเพียงใด
1. JSON กับ YAML: การบรรจุทางเดียว
ข้อกำหนด YAML 1.2 ระบุชัดเจนว่า JSON เป็นซูเบตของ YAML นั่นคือ JSON ที่ถูกต้องใดก็เป็น YAML ที่ถูกต้อง:
{"name": "json", "tags": ["a", "b"], "count": 3}
ตัวอย่างนี้เป็นทั้ง JSON และ YAML แบบ flow style ทิศทางย้อนกลับไม่จริง
| คุณสมบัติ | JSON | YAML 1.2 |
|---|---|---|
| คอมเมนต์ | ✗ | ✓ |
| Anchor และ alias | ✗ | ✓ |
เอกสารหลายชิ้น (---) |
✗ | ✓ |
| คีย์ที่ไม่ใช่สตริง | ✗ | ✓ (1: a, true: b) |
| คีย์แบบซับซ้อน | ✗ | ✓ (? [a, b]) |
| ไวต่อการย่อหน้า | ✗ | ✓ (การย่อหน้าด้วยแท็บถูกปฏิเสธ) |
| การอนุมานชนิดข้อมูล | ชัดเจน | โดยนัย (0o17, 0x1F, .inf) |
| ความอ่านง่ายบนบรรทัดเดียว | ดี | แย่ (เนื้อหายาวต้องพึ่งไวยากรณ์พับบรรทัด) |
1.1 การอนุมานชนิดข้อมูลคือกับดักที่ใหญ่ที่สุด
YAML แยกวิเคราะห์ค่าบางค่าที่ดูเหมือนสตริงเป็นชนิดอื่น:
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
ตัวอย่าง NO เคยเผาคนมาแล้วหลายราย: กับตัวแยกวิเคราะห์ YAML 1.1 (ไลบรารีเก่าบางตัว) country: NO กลายเป็น false
มาตรการรับมือ: ใส่เครื่องหมายคำพูดให้ทุกตัวระบุที่ "ดูเหมือนตัวเลขหรือบูลีน" เช่น version: "1.10"
1.2 Anchor, alias และเอกสารหลายชิ้น
defaults: &defaults
retries: 3
timeout: 30
serviceA:
<<: *defaults
host: a.example.com
---
serviceB:
<<: *defaults
host: b.example.com
หลังขยาย anchor และแปลงเป็น JSON คุณมักได้ object คอนฟิกที่ซ้ำกันสองชิ้น หรือได้ array หนึ่งชิ้น (เอกสารหลายชิ้นต้องกลายเป็น [{...}, {...}])
ไม่ทางใดก็ทางหนึ่ง anchor ไม่ถูกกู้คืนในการแปลงย้อนกลับ ความสูญเสียนั้นหลีกเลี่ยงไม่ได้
1.3 การแปลงที่รักษาคอมเมนต์
ไลบรารีส่วนใหญ่แค่ทิ้งคอมเมนต์ระหว่างแปลง เมื่อต้องเก็บไว้ ให้ใช้ Document API ของแพ็กเกจ yaml:
import { parseDocument } from 'yaml';
const document = parseDocument(`
# Service configuration
port: 8080
`);
document.set('port', 3000);
console.log(document.toString());
// # Service configuration
// port: 3000
การทดสอบง่ายมาก: ถ้าไฟล์นี้จะถูกมนุษย์แก้ซ้ำแล้วซ้ำเล่า อย่าใช้เส้นทาง "แยกวิเคราะห์ → object → ทำให้เป็นอนุกรม"
1.4 จะตรวจความสอดคล้องของการไป-กลับอย่างไร
/**
* 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);
}
การรันกับข้อมูลจริงมีค่ามากกว่าการอ่านเอกสารสิบรอบ ให้แน่ใจว่าชุดตัวอย่างครอบคลุม: null สตริงว่าง array ว่าง "00" "1e5" ลบศูนย์ จำนวนเต็มที่ใหญ่กว่า 2^53 และสตริงยาวที่มีการขึ้นบรรทัดใหม่
2. JSON กับ XML: ไม่มีมาตรฐานการแมป
XML เกิดก่อน JSON นานมากและมีความสามารถเชิงโครงสร้างแข็งแกร่งกว่ามาก (attribute, namespace, เนื้อหาแบบผสม, DTD) ระหว่างสองอย่างนี้ ไม่มีข้อกำหนดการแมปอย่างเป็นทางการ; เครื่องมือทุกตัวทำสิ่งเดียวกัน: เลือกข้อตกลงหนึ่งชุด แล้วพยายามไม่ให้สูญเสียมาก
2.1 ข้อตกลงที่พบบ่อย
| ข้อตกลง | การแทน | ตัวอย่าง |
|---|---|---|
| องค์ประกอบ → คีย์ของ object | องค์ประกอบชื่อเดียวกันกลายเป็นฟิลด์ | <a>1</a> → {"a": "1"} |
Attribute → คำนำหน้า @ |
@name |
<a id="1"/> → {"a": {"@id": "1"}} |
Text → #text |
เมื่อมีทั้ง attribute และ text | <a id="1">hi</a> → {"a": {"@id": "1", "#text": "hi"}} |
| องค์ประกอบซ้ำ → array | ปรากฏสองครั้งขึ้นไปกลายเป็น array | <i>1</i><i>2</i> → {"i": ["1","2"]} |
| Namespace → คำนำหน้า | 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 ความสูญเสียห้าประการที่หลีกเลี่ยงไม่ได้
- ลำดับองค์ประกอบ: object ของ JSON ไม่มีลำดับ ขณะที่ XML เป็นลำดับขององค์ประกอบที่มีลำดับ;
- Attribute กับองค์ประกอบลูก: ทั้งคู่มีความหมายต่างกันใน XML แต่กลายเป็นฟิลด์ธรรมดาใน JSON;
- เนื้อหาแบบผสม: โครงสร้างอย่าง
<p>你好 <b>世界</b> 再见</p>แสดงด้วย object ธรรมดาไม่ได้; - การประกาศ namespace:
xmlnsเป็นแนวคิดเรื่องขอบเขต; เมื่อแบนเป็นฟิลด์แล้วขอบเขตก็หายไป; - CDATA และ entity:
<![CDATA[...]]>และ&ใน JSON เป็นเพียงข้อความ
2.3 ทางสองแพร่งของ "องค์ประกอบเดียวหรือ array"
<list><item>1</item></list>
เมื่อมี item เดียว xml2js คืน object โดยค่าเริ่มต้นและคืน array ก็ต่อเมื่อมีสองตัว
ด้วยเหตุนี้โค้ดฟรอนต์เอนด์จึงต้องเขียน Array.isArray(x) ? x : [x]
มาตรการรับมือ: เปิดตัวเลือก "always array" ตอนแยกวิเคราะห์ หรือประกาศฟิลด์เป็นชนิด array ตาม 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 ข้อเตือนด้านความปลอดภัย
ก่อนแยกวิเคราะห์ XML ที่ไม่น่าไว้วางใจ ต้องปิด external entity ไม่อย่างนั้นอาจกระตุ้น XXE (อ่านไฟล์ในเครื่อง ตรวจเครือข่ายภายใน):
// In the Node ecosystem, DTDs and external entities are usually disabled through parser options
const parserOptions = { doctype: false, externalEntities: false };
XML → JSON เป็นเพียงชั้นแรกของการแปลง; การแปลงไม่ช่วยกำจัดพื้นผิวการโจมตีที่ XML นำมา
3. สามรูปแบบเหมาะกับอะไร
| สถานการณ์ | รูปแบบที่แนะนำ | เหตุผล |
|---|---|---|
| HTTP API สาธารณะ | JSON | เบราว์เซอร์และทุกภาษารองรับโดยตรง |
| คอนฟิกที่ดูแลด้วยมือ | YAML | คอมเมนต์ การย่อหน้า ความอ่านง่าย |
| เอกสารที่ต้องมี schema เข้มงวดและ namespace (SOAP, Office Open XML, SVG) | XML | ความสามารถเชิงโครงสร้างและระบบนิเวศที่มีอยู่ |
| การทำงานร่วมกับเครื่องมือปฏิบัติการ (K8s, CI) | YAML | ค่าเริ่มต้นของระบบนิเวศ |
| การจัดเก็บถาวร แคช log | JSON / JSONL | สากลและไม่ต้องพึ่ง dependency |
4. บันทึกการนำไปใช้งาน
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',
});
}
ตัวเลือกที่ใช้ได้จริงสองสามข้อ:
lineWidth: 0ปิดการพับบรรทัดอัตโนมัติ; ไม่เช่นนั้น URL ยาวจะถูกตัดข้ามบรรทัดและอ่านยาก;- การใส่เครื่องหมายคำพูดคู่ทุกค่าช่วยป้องกันไม่ให้ค่าอย่าง
NOและ1.10ถูกอ่านผิด; - สำหรับข้อมูลที่มีความแม่นยำตัวเลขอ่อนไหว ให้แปลงเป็นสตริงก่อนทำให้เป็นอนุกรม
สำหรับทิศทาง XML ให้ตรึงฟังก์ชันแมปหนึ่งตัวและใส่ "คำนำหน้า attribute", "ชื่อคีย์ text" และ "กลยุทธ์ array" ไว้ใน signature แทนการกระจายไปทั่ว:
type XmlMappingOptions = {
attributePrefix: string;
textKey: string;
alwaysArray: boolean;
};
const defaultMapping: XmlMappingOptions = {
attributePrefix: '@',
textKey: '#text',
alwaysArray: false,
};
5. รายการตรวจสอบ
- [ ] ตัวระบุทุกตัวที่ "ดูเหมือนตัวเลข/บูลีน" ใน YAML ถูกใส่เครื่องหมายคำพูด;
- [ ] ชุดตัวอย่างตรวจสอบสำหรับ JSON → YAML → JSON ถูกกำหนดและครอบด้วยเทสต์;
- [ ] เมื่อต้องรักษาคอมเมนต์ ใช้ Document API แทนการไป-กลับผ่าน object;
- [ ] ข้อตกลงการแมป XML ถูกเขียนไว้และทีมใช้พารามิเตอร์ชุดเดียวกัน;
- [ ] DTD และ external entity ถูกปิดเมื่อแยกวิเคราะห์ XML ที่ไม่น่าไว้วางใจ;
- [ ] จำนวนไบต์และจำนวนฟิลด์ถูกเปรียบเทียบก่อนและหลังแปลง และความผิดปกติใด ๆ ต้องแจ้งเตือน
คำถามที่พบบ่อย
ควรเลือกทิศทางการแปลงระหว่าง JSON, YAML และ XML อย่างไร
ให้ใช้ JSON เป็นรูปแบบกลาง: แยกวิเคราะห์ YAML และ XML เป็นโมเดลข้อมูลก่อน แล้วทำให้เป็นอนุกรมจากโมเดลนั้นแทนการเขียนข้อความใหม่ ใช้ Document API ของ YAML เมื่อต้องรักษาคอมเมนต์ และตรึงข้อตกลงการแมป XML หนึ่งชุดก่อนจะต้องใช้ attribute หรือ namespace
ทำไมการแปลง XML เป็น JSON ถึงสูญเสีย attribute และ namespace
JSON ไม่มีแนวคิดเรื่อง attribute หรือ namespace attribute คำนำหน้า และ URI ของ namespace ใน XML จึงต้องถูกพับเป็นคีย์ธรรมดา มักใช้คำนำหน้า @ หรือโหนด #text เมื่อกฎการแมปต่างกัน XML เดียวกันจะให้ JSON ที่ต่างกันและไป-กลับไม่ได้
ควรซิงค์ JSON, YAML และ XML สองทิศทางในระยะยาวหรือไม่
ไม่ควร คอมเมนต์ anchor attribute และ namespace ของ XML ล้วนสูญเสียข้ามรูปแบบ และการซิงค์สองทิศทางจะสะสมความต่างไปเรื่อย ๆ ให้เลือกรูปแบบต้นทางหนึ่งอย่างและถือรูปแบบอื่นเป็นไฟล์ส่งออกชั่วคราวหรือมุมมองชั่วคราว