那份部署用的 YAML 里有三行很普通的东西:image: 11.10、debug: on、mode: 0644。我把整份文件丢进转换器要一份 JSON,再转回 YAML 提交,diff 里跳出三处谁都没改过的东西:11.10 少了末尾的零、on 换成 true、0644 变成四百多。这不是转换器坏了,而是 YAML 的隐式类型解析在动手:它不接受「这段文字就是这段文字」,每个标量都要先猜一次类型,再决定怎么写进 JSON。
踩过的坑分成三类:被当成布尔或数字的字符串、被静默吃掉的重复键,以及锚点与多文档这类引用结构。我在 YAML 与 JSON 互转 上把一批标量逐条喂进去,把「输入怎么写」和「输出什么类型」抄成一张对照表。
一张对照表:哪些标量会被换掉
| 输入写法 | 默认读成的值 | 换档或加引号之后 |
|---|---|---|
yes / No / ON / off | 布尔 true / false | 严格档全部退回字符串 |
y / n / yEs / oFf | 原样字符串 | 混合大小写一律不判布尔 |
true / TRUE / False | 布尔 | 两种档位都判布尔 |
11.10 / 2.0 / 0.50 | 11.1、2、0.5 | 加引号才保住末尾的零 |
11.01 / 1.2.3 | 11.01 是数字,1.2.3 是字符串 | 三段点分不会被当成数字 |
007 / 0x1F / 0o17 / 0b101 | 7、31、15、5 | 显式前缀与档位无关 |
08 / 09 | 整串退回字符串 | 严格档按十进制读成 8、9 |
1_000 / .5 / 1e3 | 1000、0.5、1000 | 严格档 1_000 变字符串 |
2026-10-23 / 12:30:00 / 2026-13-45 | 全是字符串,0 条警告 | 不转 Date,也不校验日期合法性 |
.inf / .nan | 带引号的 "Infinity"、"NaN",各挂 1 条警告 | JSON 本身没有这两个字面量 |
9007199254740993 | 9007199254740992 | 越过安全整数后末位被舍入 |
布尔词表:33 个词里 15 个不被当成布尔
默认档把 YAML 1.1 的布尔词表整批扫一遍:33 个候选写法里 9 个判 true、9 个判 false、15 个保持字符串。收窄的两处:单字母 y 和 n 不算布尔,混合大小写的 yEs、oFf 也一律原样;带前导或尾随空格的 true 同样不判。写的时候差一个字母,读出来的类型就换了档。
这条差异落在配置文件上很直观:a: no 与 b: on 出来是 "a": false, "b": true,而 c: y 与 f: n 仍是字符串。反方向做 JSON 转 YAML 时,危险键名会被补上引号——"on": 1 落成 'on': 1,键位不会被下一次读回时吃掉。
前导零不是十进制:0644 读出 420
默认档按 YAML 1.1 读前导零:007 是 7,017 是 15,011 是 9,0644 是 420,00 是 0。文件权限、分片编号、工单号这类字符串最容易被这一条改动。更怪的是它只在八进制位合法时才换算:08 和 09 因为出现非法八进制位,整串退回字符串。
显式前缀与档位无关:0x1F 是 31、0o17 是 15、0b101 是 5;残缺的 0x、0b 原样留成字符串。下划线也被直接删掉,1_000 变 1000,1_0.5 变 10.5。
界面右上角那枚严格档开关不是只管布尔,它同时改写了 6 处数字读法:011 从 9 变 11、017 从 15 变 17、08 与 09 从字符串变 8 和 9、1_000 从 1000 变回字符串、1.5e3 从字符串变 1500。改的是整套读法,不是只有 yes/no 那几个词。
哪些字段最容易中招
比例不算吓人,会出问题的字段有共同点:看着像数字的字符串(版本号、端口、权限位)、看着像英文的开关值(on、no、off),以及看着不是日期的日期——12:30:00 这类六十进制写法一律保持字符串,不会被乘 60。
图 1:布尔词表扫描的 9 / 9 / 15 归属
精度这条线画在哪里
浮点保留 17 位有效数字,0.30000000000000004 这类值原样输出,不会被圆到 0.3。断点是整数的安全上界:越过 9007199254740991 就掉精度,9007199254740993 出来是 9007199254740992,末位被双精度舍掉,这种丢法没有任何提示。超长 ID、雪花号先加引号再转。
输入侧没有长度上限,一份 20000 行、397779 字节的 YAML 走完是 85 毫秒、产出 537782 字节。反方向也不会把大整数写成科学计数,9007199254740992 写回 YAML 仍是那一长串。
重复键:覆盖发生在前,提示只在一半的场景里
b: 1 / a: 2 / b: 3 三行出来是 "b": 3 和 "a": 2,值取后面那次,键位仍按首次出现的顺序排。合并会挂一条警告:「第 3 行:键 b 重复出现,后面的值覆盖了前面的值」。同一个键写 n 行,警告条数正好是行数减 1,最后一条落在发生覆盖的那行。
两处例外。带引号的键与裸键算同一个键:"k": 1 配 k: 2 合成一个键并给 1 条提示。写成流式集合则不一样:a: {k: 1, k: 2} 覆盖照样发生,结果只剩 "k": 2,警告却是 0 条。靠警告列表判断有没有被覆盖并不可靠。
图 2:重复键行数与警告条数
锚点全部展开,合并键只补空
JSON 没有锚点这个概念,转过去的那一刻引用就被摊平成实际值。a: &anchor hello 配 b: *anchor 出来是两个 "hello";别名指向序列时按深拷贝落进结果。
合并键 << 的优先级容易记反:它只补空,不覆盖。基里给 a: 1、c: 3,子节点自己写 c: 9,展开后 a 补进来、c 仍是 9。列了多个锚点时,后写出的先落键,本节点的值照样赢。
s: &s [1, 2]
d:
<<: *s
合并键指向序列而不是映射时,结果是空对象,并给 2 条同样的「第 3 行:合并键 << 引用的不是映射,已跳过」。三类失败用法共用另一条文案:前向引用(先 *a 后 &a)、引用没定义过的名字、循环别名,都停在「引用了未定义的锚点」上且行列坐标齐全。
Q:版本号这类值怎么保持字符串?
加引号最稳,"11.10"、"no" 走完全程形状不变。布尔词那半边还能勾严格档退回字符串,但这一档同时改了 6 处数字读法,划不来。
Q:缩进里混了 Tab 会被指出来吗?
只有缩进位置的 Tab 会被拦,报错带行列并提示换成空格;写在值中间或流式集合里的 Tab 被当空白跳过,一条提示都没有。和同级混排时,报的还是「缩进不一致」。
Q:一次能转多份文本吗?
多文档是唯一批量形态:用 --- 分隔并勾选「多文档」开关,输出变顶层数组,空文档落成 null 元素。没勾开关时 2 个文档会被整条拒绝,这句报错坐标固定是 line=0 col=0,界面上看不到行列信息。