Ranh giới chuyển đổi JSON ↔ YAML và XML
Kết luận: Tuyên bố rằng JSON là tập con của YAML chỉ đảm bảo một chiều, còn XML hoàn toàn không có ánh xạ JSON chính thức — mọi thứ đều dựa trên quy ước.

Kết luận: dùng JSON làm định dạng trung gian — phân tích nguồn thành mô hình dữ liệu rồi tuần tự hóa sang đích; trước khi chuyển đổi, hãy quyết định xem comment, thuộc tính XML và namespace, cùng kiểu số hoặc boolean có cần sống sót hay không.
Một pipeline chuyển đổi có dùng được hay không không phụ thuộc vào việc đầu ra trông đẹp đến đâu, mà vào việc bạn có ý thức được nó đã vứt đi bao nhiêu.
1. JSON và YAML: quan hệ chứa một chiều
Đặc tả YAML 1.2 nói thẳng: JSON là tập con của YAML. Nghĩa là mọi JSON hợp lệ đều là YAML hợp lệ:
{"name": "json", "tags": ["a", "b"], "count": 3}
Đoạn này vừa là JSON vừa là YAML flow style. Chiều ngược lại không đúng.
| Tính năng | JSON | YAML 1.2 |
|---|---|---|
| Comment | ✗ | ✓ |
| Anchor và alias | ✗ | ✓ |
Nhiều tài liệu (---) |
✗ | ✓ |
| Khóa không phải chuỗi | ✗ | ✓ (1: a, true: b) |
| Khóa phức hợp | ✗ | ✓ (? [a, b]) |
| Nhạy cảm với thụt lề | ✗ | ✓ (thụt lề bằng tab bị từ chối) |
| Suy luận kiểu | tường minh | ngầm định (0o17, 0x1F, .inf) |
| Khả năng đọc trên một dòng | tốt | kém (nội dung dài phụ thuộc cú pháp folding) |
1.1 Suy luận kiểu là bẫy lớn nhất
YAML phân tích một số giá trị trông như chuỗi thành kiểu khác:
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
Ví dụ NO đã làm khổ không ít người: với parser YAML 1.1 (một số thư viện cũ), country: NO trở thành false.
Biện pháp: đặt trong ngoặc mọi định danh "trông giống số hoặc boolean", như version: "1.10".
1.2 Anchor, alias và nhiều tài liệu
defaults: &defaults
retries: 3
timeout: 30
serviceA:
<<: *defaults
host: a.example.com
---
serviceB:
<<: *defaults
host: b.example.com
Sau khi mở rộng anchor rồi chuyển sang JSON, bạn thường nhận được hai object cấu hình trùng lặp, hoặc một mảng (nhiều tài liệu phải trở thành [{...}, {...}]).
Dù theo cách nào, anchor không được phục hồi khi chuyển ngược — mất mát đó là không thể tránh.
1.3 Chuyển đổi mà vẫn giữ được comment
Hầu hết thư viện chỉ đơn giản bỏ comment khi chuyển đổi. Khi cần giữ chúng, hãy dùng Document API của gói yaml:
import { parseDocument } from 'yaml';
const document = parseDocument(`
# Service configuration
port: 8080
`);
document.set('port', 3000);
console.log(document.toString());
// # Service configuration
// port: 3000
Thử nghiệm rất đơn giản: nếu tệp này sẽ bị con người chỉnh đi chỉnh lại nhiều lần, đừng đi theo con đường "parse → object → serialize".
1.4 Cách kiểm tra nhất quán khứ hồi
/**
* 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);
}
Chạy nó trên dữ liệu thật đáng giá hơn đọc tài liệu mười lần. Hãy đảm bảo bộ mẫu của bạn bao phủ: null, chuỗi rỗng, mảng rỗng, "00", "1e5", âm không, số nguyên lớn hơn 2^53, và chuỗi dài có chứa xuống dòng.
2. JSON và XML: không có ánh xạ chuẩn
XML ra đời trước JSON rất lâu và có năng lực cấu trúc mạnh hơn nhiều (thuộc tính, namespace, nội dung hỗn hợp, DTD). Giữa hai định dạng không có đặc tả ánh xạ chính thức; mọi công cụ đều làm cùng một việc: chọn một quy ước, rồi cố gắng không mất quá nhiều.
2.1 Quy ước phổ biến
| Quy ước | Biểu diễn | Ví dụ |
|---|---|---|
| Phần tử → khóa object | Phần tử cùng tên trở thành một trường | <a>1</a> → {"a": "1"} |
Thuộc tính → tiền tố @ |
@name |
<a id="1"/> → {"a": {"@id": "1"}} |
Text → #text |
Khi có cả thuộc tính và text | <a id="1">hi</a> → {"a": {"@id": "1", "#text": "hi"}} |
| Phần tử lặp → mảng | Hai lần xuất hiện trở lên thì thành mảng | <i>1</i><i>2</i> → {"i": ["1","2"]} |
| Namespace → tiền tố | 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 Năm mất mát không thể tránh
- Thứ tự phần tử: object JSON không có thứ tự, trong khi XML là một chuỗi phần tử có thứ tự;
- Thuộc tính so với phần tử con: chúng mang ngữ nghĩa khác nhau trong XML, nhưng đều trở thành trường thường trong JSON;
- Nội dung hỗn hợp: các cấu trúc như
<p>你好 <b>世界</b> 再见</p>không thể biểu diễn bằng object thuần; - Khai báo namespace:
xmlnslà khái niệm phạm vi; một khi bị làm phẳng thành trường, phạm vi biến mất; - CDATA và entity:
<![CDATA[...]]>và&trong JSON chẳng qua chỉ còn là văn bản.
2.3 Lưỡng nan "một phần tử hay một mảng"
<list><item>1</item></list>
Với một item duy nhất, xml2js mặc định trả về object và chỉ trả về mảng khi có hai phần tử trở lên.
Hệ quả là mã front-end phải viết Array.isArray(x) ? x : [x].
Biện pháp: bật tuỳ chọn "luôn mảng" khi phân tích, hoặc khai báo trường là kiểu mảng dựa trên 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 Nhắc nhở bảo mật
Trước khi phân tích XML không tin cậy, bạn buộc phải tắt external entity, nếu không có thể kích hoạt XXE (đọc tệp cục bộ, dò mạng nội bộ):
// In the Node ecosystem, DTDs and external entities are usually disabled through parser options
const parserOptions = { doctype: false, externalEntities: false };
XML → JSON chỉ là tầng chuyển đổi đầu tiên; chuyển đổi không xoá bỏ bề mặt tấn công mà XML mang theo.
3. Mỗi định dạng phù hợp ở đâu
| Tình huống | Định dạng đề xuất | Lý do |
|---|---|---|
| API HTTP công khai | JSON | Được trình duyệt và mọi ngôn ngữ hỗ trợ sẵn |
| Cấu hình do con người duy trì | YAML | Comment, thụt lề, khả năng đọc |
| Tài liệu cần schema mạnh và namespace (SOAP, Office Open XML, SVG) | XML | Năng lực cấu trúc và hệ sinh thái sẵn có |
| Tương tác với công cụ vận hành (K8s, CI) | YAML | Mặc định của hệ sinh thái |
| Lưu trữ, cache, log | JSON / JSONL | Phổ quát và không phụ thuộc |
4. Ghi chú triển khai
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',
});
}
Một vài tuỳ chọn thực tế:
lineWidth: 0tắt tự động xuống dòng; nếu không, URL dài bị cắt sang nhiều dòng và trở nên khó đọc;- đặt ngoặc kép cho mọi thứ giúp các giá trị như
NOvà1.10không bị hiểu nhầm; - với dữ liệu có độ chính xác số nhạy cảm, hãy chuyển sang chuỗi trước khi tuần tự hoá.
Với chiều XML, hãy cố định một hàm ánh xạ duy nhất và đưa "tiền tố thuộc tính", "tên khóa text" và "chiến lược mảng" vào chữ ký của nó thay vì rải rác khắp nơi:
type XmlMappingOptions = {
attributePrefix: string;
textKey: string;
alwaysArray: boolean;
};
const defaultMapping: XmlMappingOptions = {
attributePrefix: '@',
textKey: '#text',
alwaysArray: false,
};
5. Danh sách kiểm tra
- [ ] Mọi định danh "trông giống số/boolean" trong YAML đều được đặt trong ngoặc;
- [ ] Bộ mẫu kiểm chứng cho JSON → YAML → JSON được định nghĩa và bao phủ bằng test;
- [ ] Khi phải giữ comment, dùng Document API thay vì khứ hồi qua object;
- [ ] Quy ước ánh xạ XML được ghi lại và cả nhóm dùng chung một bộ tham số;
- [ ] DTD và external entity bị tắt khi phân tích XML không tin cậy;
- [ ] Số byte và số trường được so sánh trước và sau chuyển đổi, bất kỳ bất thường nào cũng phát cảnh báo.
FAQ
Làm sao chọn hướng chuyển đổi giữa JSON, YAML và XML?
Ưu tiên JSON làm định dạng trung gian: phân tích YAML và XML thành mô hình dữ liệu trước, rồi tuần tự hoá từ mô hình đó thay vì viết lại văn bản. Dùng Document API của YAML khi cần giữ comment, và cố định một quy ước ánh xạ XML trước khi bạn cần đến thuộc tính hoặc namespace.
Vì sao chuyển XML sang JSON lại làm mất thuộc tính và namespace?
JSON không có khái niệm thuộc tính hay namespace, nên thuộc tính XML, tiền tố và URI namespace buộc phải được gộp vào khóa thường, thường là với tiền tố @ hoặc node #text. Một khi quy tắc ánh xạ khác nhau, cùng một XML sẽ tạo ra JSON khác nhau và không thể khứ hồi.
Có nên giữ JSON, YAML và XML đồng bộ hai chiều lâu dài không?
Không. Comment, anchor, thuộc tính XML và namespace đều bị mất qua các định dạng, và đồng bộ hai chiều cứ tích lũy khác biệt. Hãy chọn một định dạng nguồn và coi các định dạng còn lại là bản xuất một lần hoặc góc nhìn tạm thời.