YAML、CSV、XML、JSON 看起来都能”装数据”,但底层模型差异巨大:CSV 是二维表、YAML / JSON 是树、XML 是带属性的树兼文档。互转时哪些信息能保、哪些必丢,取决于源和目标的模型差距。
四种格式的本质对比
| 格式 | 数据模型 | 类型推断 | 嵌套 | 数组 | 属性 | 注释 |
|---|---|---|---|---|---|---|
| CSV | 二维表 | 全字符串 | ✗ | ✗ | ✗ | ✗ |
| JSON | 树 | string/num/bool/null | ✓ | ✓ | ✗ | ✗ |
| YAML | 树 | 自动推断(可显式) | ✓ | ✓ | ✗ | ✓ |
| XML | 树 + 属性 + 文档 | 全字符串(schema 可定) | ✓ | 重复元素 | ✓ | ✓ |
转换的”信息损失”按方向:
XML 属性 → JSON / YAML / CSV [需要约定]
JSON / YAML 嵌套 → CSV [必须扁平化]
JSON 数组 → CSV [需要扩展列或多行]
YAML 注释 → JSON [必丢]
任何 → CSV [类型变字符串]
CSV 三大坑
1. 编码与 BOM
CSV 没有强制编码——Windows 下 Excel 默认按系统区域读,中文系统是 GBK。UTF-8 不带 BOM 时中文乱码:
姓名,年龄 ← UTF-8 文件
濮撳悕,骞撮緞 ← Excel 按 GBK 解码后看到的中文乱码
修法:UTF-8 + BOM(开头三字节 0xEF 0xBB 0xBF)。Excel 看到 BOM 自动识别 UTF-8。
但 BOM 在某些场景反而是坑:
# Linux 下处理带 BOM 的 CSV
$ awk -F, '{print $1}' data.csv
\xef\xbb\xbf姓名 ← 第一行第一列被吃了 BOM
对策:给 Excel / WPS 看的 CSV 加 BOM;给 awk / cut / pandas 处理的不加。本工具支持选择是否加 BOM 输出。
2. 引号转义
CSV 标准 RFC 4180:字段内含逗号、双引号、换行时必须用双引号包裹,字段内的双引号写两遍("")。
name,bio
Alice,"Loves ""coding"" and reading"
Bob,"Lives in Beijing, China"
实战中相当一部分”野生 CSV”不严格遵守 RFC 4180:
- 用
\"转义而不是""(误用 SQL 风格) - 字段含换行时不加引号
- 引号开头不结尾
解析这类”野生 CSV”必须用宽容模式或写自定义分隔符。本工具默认 RFC 4180 严格模式,遇到错误会指出位置。
3. 类型全丢
CSV 所有字段都是字符串。从 JSON 转 CSV 后:
{"id": 123, "active": true, "score": null}
变成:
id,active,score
123,true,
但反向转换时——123 是 int 还是 string?true 是 bool 还是字符串 “true”?"" 空字段是 null 还是空串?
没有 schema 就猜不准。本工具的”类型推断”按列扫描,全列纯数字推断为 number,true/false 推断为 bool,空格推断为 null——但这是启发式,复杂场景需要手动指定。
YAML 的缩进雷
YAML 用缩进表达层级,必须用空格——Tab 一律报错:
# 错误:Tab 缩进
parent:
child: value # 这里是 Tab,YAML 解析器报 "found character that cannot start any token"
# 正确:2 空格
parent:
child: value
复制粘贴时编辑器常自动把 Tab 转空格或反之,让人困惑。常见 IDE 设置:
VS Code: settings.json
"[yaml]": {
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.detectIndentation": false
}
另一个坑是缩进数量必须全文一致:
# 错误:父子用 2,孙用 4
parent:
child:
grandchild: value # 这里突然 4 空格
合法但会让维护者混乱。开源项目 PR 常常因为缩进风格被打回来。
XML 属性映射 JSON
XML 有 element 和 attribute 两种”装数据”的方式:
<book id="1" lang="zh">
<title>红楼梦</title>
<author>曹雪芹</author>
</book>
转 JSON 时无标准规则。常见三种约定:
@ 前缀法(fast-xml-parser、xml2js 等多数库默认):
{
"book": {
"@id": "1",
"@lang": "zh",
"title": "红楼梦",
"author": "曹雪芹"
}
}
属性加 @ 前缀,元素直接作为字段。简单且可双向转换,本工具采用这种。
Badgerfish(更严格):
{
"book": {
"@id": "1",
"@lang": "zh",
"title": { "$": "红楼梦" },
"author": { "$": "曹雪芹" }
}
}
属性 @、文本节点 $。每个元素都是对象,能区分纯文本和带子元素,但更冗长。
简化合并(属性 / 元素同名空间):
{
"book": {
"id": "1",
"lang": "zh",
"title": "红楼梦",
"author": "曹雪芹"
}
}
简洁但不能反向转换——分不清哪些是原本的属性。
嵌套扁平化策略
JSON / YAML 转 CSV 时必须扁平化嵌套:
{
"name": "Alice",
"address": {
"city": "Beijing",
"zip": "100000"
},
"tags": ["coder", "reader"]
}
常见扁平化方案:
点号路径:
name,address.city,address.zip,tags.0,tags.1
Alice,Beijing,100000,coder,reader
下划线路径:
name,address_city,address_zip,tags_0,tags_1
Alice,Beijing,100000,coder,reader
JSON 字符串作单列:
name,address,tags
Alice,"{""city"":""Beijing"",""zip"":""100000""}","[""coder"",""reader""]"
前两种适合”基本平的对象”,第三种适合”复杂嵌套保结构”。本工具支持三种模式选择。
不规则数据的扁平化噩梦
每行嵌套深度不一致时尤其痛苦:
[
{"name": "Alice", "addresses": [{"city": "Beijing"}]},
{"name": "Bob", "addresses": [{"city": "Shanghai"}, {"city": "Hangzhou"}]}
]
扁平化后:
name,addresses_0_city,addresses_1_city
Alice,Beijing,
Bob,Shanghai,Hangzhou
列数等于”出现过的最深路径数”,第一行的 addresses_1_city 是空。数据集大时空列爆炸。
更稳的替代是 JSONL(每行一个独立 JSON):
{"name": "Alice", "addresses": [{"city": "Beijing"}]}
{"name": "Bob", "addresses": [{"city": "Shanghai"}, {"city": "Hangzhou"}]}
JSONL 既能流式处理(每行独立解析)、又保留嵌套,是大数据处理的事实标准。
一句话总结
CSV 适合二维数据 + 给人看,JSON / YAML 适合嵌套 + 给程序,XML 适合文档 + 强 schema;不要硬把树塞进表,能 JSONL 就别 CSV。