YAML / CSV / XML 互转的三个坑:缩进、引号、命名空间

· 约 4 分钟 YAML/CSV/XML 互转

YAML、CSV、XML、JSON 看起来都能”装数据”,但底层模型差异巨大:CSV 是二维表、YAML / JSON 是树、XML 是带属性的树兼文档。互转时哪些信息能保、哪些必丢,取决于源和目标的模型差距。

四种格式的本质对比

格式数据模型类型推断嵌套数组属性注释
CSV二维表全字符串
JSONstring/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。

❓ 常见问题

CSV 中文乱码总是 UTF-8 BOM 的锅吗?

一半是。Excel 在 Windows 下打开 CSV 默认按系统区域编码(中文系统是 GBK),文件如果是 UTF-8 不带 BOM 就乱码;带 BOM 就识别正确。解决方案:导出 CSV 时加 UTF-8 BOM(开头 0xEF 0xBB 0xBF 三字节),Excel 就能识别。但有些工具加 BOM 会让 Linux 下的 awk / cut 处理失败(把 BOM 当成第一行第一列的内容)——所以"给人看用 BOM,给程序用不带 BOM"。Mac Excel、WPS、LibreOffice 默认 UTF-8,不需要 BOM。

YAML 缩进用 2 空格还是 4 空格?Tab 行不行?

Tab 一律不行——YAML 1.2 规范明确禁止用 Tab 作缩进字符(理由是 Tab 在不同环境下宽度不一致)。空格数量没强制要求,全文一致即可——同一文档里 2 空格和 4 空格混用会报错。社区惯例是 Kubernetes / Ansible 用 2 空格部分 CI 配置用 4 空格。新文档建议 2 空格(更紧凑,多嵌套层级时不至于太靠右)。复制粘贴别人的 YAML 时一定要确认缩进是空格还是 Tab,编辑器经常默认 Tab 把空格替换。

XML 转 JSON / YAML 后属性 (attr) 跑哪去了?

各转换器策略不同,常见三种:@ 前缀法<a x="1">y</a>{"a": {"@x": "1", "#text": "y"}},属性加 @ 前缀、文本用 #text 之类的特殊键。合并法{"a": {"x": "1", "value": "y"}}——简洁但属性和子元素混在一起,反向转换时不知道 x 是属性还是子节点。剥离法:直接丢掉属性,只保留 {"a": "y"}——信息丢失。本工具用 @ 前缀法(fast-xml-parser 默认约定),可双向转换且属性可识别。

嵌套数据扁平化成 CSV 会丢什么?

嵌套结构和数组都会被压平{name: "x", addresses: [{city: "A"}, {city: "B"}]} 扁平化后大致变成 name, addresses_0_city, addresses_1_city 三列,列数随数据动态扩展。问题:第一行可能 2 个 addresses,第二行可能 5 个——列数不齐,CSV 表头要么取最大值(多空列)要么按行不齐(不规范)。最佳实践:先在工具里规范化数据结构(补齐字段),再转 CSV;或干脆用 JSONL(每行一个 JSON)替代 CSV——JSONL 既能流式处理又支持嵌套。

打开 YAML/CSV/XML 互转 YAML↔CSV↔XML↔JSON 六向互转·嵌套扁平化·类型推断·本地解析