JSON 与 YAML 互转

JSON 转 YAML、YAML 转 JSON,手写解析覆盖常见结构

JSON 与 YAML 互转:类型陷阱、注释丢失与安全问题

两者的关系不是对等的

YAML 1.2 是 JSON 的超集——任何合法的 JSON 文档都是合法的 YAML(少数边缘情况除外,主要是缩进里不允许制表符)。反过来不成立。

YAML 多出来的能力正是它被广泛用于配置文件的原因:

能力JSONYAML
注释不支持# 注释
多行文本必须写成 \n 转义| 与 > 块标量
锚点与引用不支持,只能重复写&anchor / *anchor
标点噪声引号、逗号、花括号都要写缩进表达结构
可读性长文档较难读层次直观

所以转换方向的性质不同:JSON → YAML 基本无损(只是换个写法);YAML → JSON 必然丢东西——注释和锚点结构都留不下来。

YAML → JSON 会丢什么

  • 注释全部消失。JSON 没有注释语法,这是硬限制。所以不要把 YAML 当唯一配置源:如果你的流程是"编辑 YAML → 转成 JSON 部署",那转出来的 JSON 不能作为下次编辑的输入,否则注释会被反复抹掉。正确的做法是保留 YAML 作为源,JSON 只作为生成产物。
  • 锚点被展开。&base 定义的片段会被复制到每个引用处,结构信息丢失,文档体积可能膨胀。反向生成锚点是启发式的(判断哪几段内容完全相同),不能指望工具替你决定复用策略。
  • 非字符串键会被强制转换。JSON 规定对象的键必须是字符串,而 YAML 允许键是数字、布尔甚至复杂结构。转换时这些键必须被字符串化,于是 123: value 变成 "123": value——原本的类型信息消失,再转回 YAML 不会自动还原。
  • 多文档(--- 分隔)无法用单个 JSON 表达,只能取其中一个文档,或者包成一个数组。

YAML 的类型推断是坑最多的部分

JSON 的类型来自引号和字面量形式,明确无歧义。YAML 允许不加引号,代价是解析器需要猜测类型,于是出现了一批著名的陷阱:

你写的值解析结果问题
NO / no / offfalse"挪威问题":国家代码 NO 变成布尔假
1.101.1版本号被当成浮点数,尾零丢失
01234不同解析器结果不同有的按八进制、有的按十进制,邮政编码灾难
12:30:0045000YAML 1.1 支持六十进制,时间被算成秒数
2026-01-15日期对象转成 JSON 时被序列化成 ISO 时间字符串,可能带时区偏移
yes / onYAML 1.1 为 true,1.2 为字符串同一份文档在不同版本下结果不同

结论很简单:凡是"看起来像数字、布尔、日期"的字符串,一律加引号。版本号 "1.10"、国家代码 "NO"、带前导零的编号 "01234",都应当写成带引号的形式。这是唯一可靠的防御方式,不能靠记忆各版本的差异。

YAML 的安全问题

  • 反序列化可以执行代码。某些语言的 YAML 库支持自定义标签,加载时会实例化任意对象(Ruby 的 Psych.load、Python 的 yaml.load 旧版行为)。绝不要用允许任意标签的加载方式处理来自不可信来源的 YAML,要用安全模式(Python 的 safe_load、带显式 schema 的加载器)。
  • 锚点炸弹(billion laughs)。用嵌套的锚点引用可以构造出体积指数膨胀的文档。一个几 KB 的输入就能吃掉几 GB 内存。这也是限制锚点展开次数的原因。
  • 解析器的实现差异。YAML 规范庞大而细节多,不同语言的实现(PyYAML、libyaml、go-yaml、ruamel)在边缘情况下行为不一致。跨语言交换数据应用 JSON,YAML 只适合人写、单语言读的配置。

本工具的边界

本工具的 YAML 解析是为常见结构手写的,覆盖缩进映射、序列、标量、引号字符串与注释。它不实现锚点与别名、自定义标签、多文档分隔、复杂的流式({} / [])嵌套等完整规范特性。

所以:日常编辑 Kubernetes 清单、CI 配置、docker-compose 这类文件,用它做互转和快速查看足够;要处理生产环境里由程序生成的 YAML(可能含锚点、复杂标量),请用对应语言的官方解析器,或者用命令行工具做权威转换,再拿结果回来核对。工具在遇到无法解析的结构时会报错并指出行号,不会给出一份静默出错的输出。

所有解析都在浏览器本地完成,配置文件内容不会被上传——这一点在配置文件常含内网地址、密钥名、账号信息时尤其重要。

常见问题

为什么字符串 "100" 转 YAML 后带引号?

为了避免再转回 JSON 时变成数字。YAML 默认会把纯数字识别成整数,加引号能精确保留原类型。

支持的 YAML 特性有限吗?

覆盖日常最常见的子集:映射、序列、嵌套、标量、注释。锚点(anchor)、多文档、复杂流式写法暂不支持。