YAML 的报错有一种特别的折磨人:文件看着没错,解析器不答应,而报错指向的行,往往离真正的问题隔着两层。这是一份实战排错手册,按出现频率排序,每条都来自有人真的贴进 issue 里的错误。
1. "mapping values are not allowed in this context"
报错原文:
mapping values are not allowed in this context
最小复现:
message: Error: not found
根因:只要出现"冒号加空格"(: ),无论在哪都会开启一个新的映射。在一个没加引号的值里,第二个冒号被当成了键分隔符。
修法:把整个值用引号包起来,冒号就只是普通字符:
message: "Error: not found"
2. "could not find expected ':'"
could not find expected ':'
name devtul
age: 30
根因:name devtul 没有冒号,解析器一直没看到键值对,读到下一行才崩。
修法:把冒号和它后面那个必须的空格补回来:
name: devtul
age: 30
3. 缩进不一致导致层级错位
server:
host: localhost
port: 8080
根因:第二个键缩进了三格,而它的兄弟键是两格。YAML 把它当成另一层;如果那一层没有别的内容,就会报错 —— 更糟的是,解析出一个你没想到的结构。
修法:让同级键的缩进严格一致。用会把空白显示出来的编辑器,存盘前就能看见错。
4. 缩进里混进了 Tab
server:
host: localhost
(箭头位置是一个真实的 Tab 字符。)根因:YAML 禁止用 Tab 缩进。很多编辑器把 Tab 显示得和空格一样宽,于是文件看着对齐、解析却失败。
修法:把 Tab 展开成空格。把编辑器设成"缩进时插入空格",重新保存。
5. "found character that cannot start any token"
found character '@' that cannot start any token
env:
- @HOME
根因:@(以及反引号)在 YAML 1.1 里是保留指示符,不能作为一个普通标量的开头,所以这一行被拒。
修法:给值加引号,或者换个开头的字符:
env:
- "@HOME"
6. 重复键被静默覆盖
name: alpha
name: beta
根因:和 JSON 不同,YAML 不要求实现拒绝重复键。多数解析器保留最后一个,于是 name 静默变成 "beta",毫无警告。
修法:同一层永远不要重复键。用 linter 能逮到;在 YAML 查看器 里解析一下,也能看到最后留下的是哪个值。
7. yes/no/on/off 被解析成布尔
enabled: no
debug: off
根因:在 YAML 1.1 语义下,这两个都成了布尔 false。一个本想读作 "no" 的功能开关,现在是个布尔。
修法:任何不是数字的 value 都加引号:
enabled: "no"
debug: "off"
8. 数字变字符串,或字符串变数字
version: 1.10
id: 007
根因:1.10 会被解析成浮点 1.1,丢掉你多半想保留的那个尾零;007 在 YAML 1.1 下是八进制的 7。反过来,count: "5" 又会把数字变成字符串,而下流代码期待的是整数。
修法:凡是字符本身重要的就加引号;只有真想要数字类型时才让数字裸奔:
version: "1.10"
id: "007"
9. 多行字符串的换行被吃掉
text: >
line one
line two
根因:折叠标量 > 会把换行变成空格,于是值是 "line one line two"。如果你要的是换行,那用错符号了。
修法:改用字面块 | 来保留每一处换行:
text: |
line one
line two
10. 锚点名字拼错
defaults: &basis
pool: 5
production:
<<: *base
根因:别名 *base 指向一个名为 basis 的锚点,而它并不存在。不同解析器下,你会得到 "unknown alias" 或者一次空的合并。
修法:让锚点和别名的名字一字不差地对上:
defaults: &base
pool: 5
production:
<<: *base
拿不准,就解析一遍
这半数错误产出的文件,在编辑器里看着都正常,只有运行时才崩。提交之前,用 YAML 查看器 跑一遍,把结构读回来 —— 上面这些错,几乎都会在解析树摊开的那一刻现形。