🚀 外服游戏 / Github / Google 加速 💰 20 刀/年的海外服务器 🐟 外出摸鱼
首页›教程›YAML 常见语法错误排查:缩进、冒号、特殊字符、T…

YAML 常见语法错误排查:缩进、冒号、特殊字符、Tab

发布于 2026-10-20ToYaml.com约 6 分钟读完

YAML 报错绝大多数出在五个地方:用了 Tab 缩进、冒号后面没有空格、值里带了冒号或井号没加引号、缩进层级对不齐、以及值以 @、*、% 这类特殊字符开头。更麻烦的是另一类"不报错但值不对"的情况:yes 变成 true、001234 变成 668。本文把这些问题逐一复现,贴出真实解析器的报错原文和修法。所有报错都是我在 Spring Boot 3.5.7(内置 SnakeYAML 2.4)里实际跑出来的。

一、先学会读报错信息

SnakeYAML 的报错固定带一句 in 'reader', line N, column M,行号和列号都从 1 开始数,下面还会把出错那一行连同一个 ^ 指针打印出来。读报错时记住两点:

先给一张速查表,后面逐条展开:

现象SnakeYAML 报错关键词常见原因
第 N 行 Tabfound character '\t(TAB)' that cannot start any token用 Tab 缩进
某行下一个键报错mapping values are not allowed here值里有 : ,或这一行缩进多了
键值全连在一起could not find expected ':'冒号后少了空格
某个值以特殊符号开头cannot start any token / while scanning an alias@ % 反引号 * 开头的值没加引号
引号相关while scanning a quoted scalar引号没闭合,或双引号里有非法转义
列表括号相关while parsing a flow sequence[ 没写 ]
同名键found duplicate key同一层级重复写了同一个键

二、Tab 缩进:YAML 不允许

YAML 规范明确规定缩进只能用空格。编辑器里 Tab 和四个空格看起来一样,是最隐蔽的错误来源。下面第二行是以 Tab 开头的:

server:
	port: 8080
while scanning for the next token
found character '\t(TAB)' that cannot start any token. (Do not use \t(TAB) for indentation)
 in 'reader', line 2, column 1:
    	port: 8080
    ^

其他解析器的说法:js-yaml 是 tab characters must not be used in indentation (2:1),PyYAML 是 found character '\t' that cannot start any token。三者都指向第 2 行第 1 列。

修法:把 Tab 换成空格。在编辑器里打开"显示空白字符",并在 VS Code 里确认右下角是 Spaces 而不是 Tab Size;也可以在项目根目录放一份 .editorconfig,对 yml 文件指定 indent_style = space。

三、冒号后缺空格:有时报错,有时静默

YAML 里键和值之间必须是 冒号 + 空格。如果写成 port:8080,解析器会把整个 port:8080 当作一个普通字符串。这个行为有两种后果。

如果这是该层级里唯一的一行,不会报错,只是值变了:

server:
  port:8080

Spring Boot、PyYAML、js-yaml 都把它解析成 server = "port:8080",而你想要的是 server.port = 8080。启动后的现象往往是"端口配置没生效",找起来很费劲。

如果同一层级还有别的键,就会报错:

server:
  port: 8080
  name:demo
  host: localhost
while scanning a simple key
 in 'reader', line 3, column 3:
      name:demo
      ^
could not find expected ':'
 in 'reader', line 4, column 3:
      host: localhost
      ^

注意它指向的是第 3 行的 name:demo,那里才是错误所在,但报错同时给出了第 4 行的位置。js-yaml 对这一段的说法是 can not read a block mapping entry; a multiline key may not be an implicit key (4:7),更难直接看懂。修法:补上空格,写成 name: demo。

四、值里含冒号或井号:要加引号

普通(不带引号)的值里,出现"冒号后跟空格"就会被当作新的键值对:

app:
  title: Hello: World
mapping values are not allowed here
 in 'reader', line 2, column 15:
      title: Hello: World
                  ^

js-yaml 的说法是 bad indentation of a mapping entry (2:15),PyYAML 和 SnakeYAML 一致,都是 mapping values are not allowed here。很多人看到 bad indentation 去检查缩进,其实是值里有冒号。修法:整个值用引号括起来:

app:
  title: "Hello: World"

反过来,: 后面不跟空格是安全的:JDBC 地址 jdbc:mysql://localhost:3306/db?useSSL=false、时间戳字符串里的冒号都不用加引号(后一种要小心类型,见第九节)。

# 的规则类似:只有"空格 + #"才是注释的开始。下面这段不会报错,但密码被截断了:

app:
  password: abc #def
  token: a#b

读出来 app.password 是 abc(#def 被当成注释丢掉了),而 app.token 是完整的 a#b。含 # 的密码、密钥务必加引号。

五、以特殊字符开头的值

YAML 把一部分符号保留作语法用途,值以它们开头时必须加引号。我实测了五个最常遇到的:

写法SnakeYAML 报错典型场景
mail: @company.comfound character '@' that cannot start any tokenMaven 资源过滤 @project.version@
pattern: *.logwhile scanning an alias ... unexpected character found .(46)通配符、日志路径
format: %d itemsfound character '%' that cannot start any token格式化模板
cmd: `ls`found character '`' that cannot start any token粘贴过来的 shell 命令
v: !importantcould not determine a constructor for the tag !important以 ! 开头被当成类型标签

以 @ 开头的完整报错如下:

app:
  mail: @company.com
while scanning for the next token
found character '@' that cannot start any token. (Do not use @ for indentation)
 in 'reader', line 2, column 9:
      mail: @company.com
            ^

以 * 开头会被当成"引用别名",因为 YAML 用 &名字 定义锚点、*名字 引用它。修法:统一加单引号:mail: '@company.com'、pattern: '*.log'。另外,{、[、|、>、&、?、以及"短横线加空格"开头的值也有特殊含义,同样建议加引号。

六、缩进对不齐

同一层级的键必须缩进相同。下面第 3 行多缩进了一个空格:

app:
  name: x
   port: 1
mapping values are not allowed here
 in 'reader', line 3, column 8:
       port: 1
           ^

这条报错和第四节的"值里有冒号"是同一句 mapping values are not allowed here,因为在解析器看来,name: x 后面的缩进更深的内容是 x 这个值的延续,延续里出现了冒号。看到这句报错,先检查报错行和它上一行的缩进是否一致,再检查值里有没有冒号。

列表短横线对不齐:静默出错

列表更危险,因为有些错位不会报错:

app:
  hosts:
  - a
   - b

Spring Boot、PyYAML、js-yaml 都不报错,三个解析器都把它读成只有一个元素的列表,元素内容是字符串 "a - b"。你本来想要的是两个元素 a 和 b。列表写完以后数一数元素个数,或者用 YAML ⇄ JSON 工具 转成 JSON 看一眼数组长度,能避开这类错误。

另外一个常见错误是在键值对之间混入列表项:

app:
  a: 1
  - b
while parsing a block mapping
 in 'reader', line 2, column 3:
      a: 1
      ^
expected <block end>, but found '-'
 in 'reader', line 3, column 3:
      - b
      ^

七、引号:没闭合、转义和单双引号的区别

双引号没有闭合时,解析器会一直读到文件末尾才报错,所以报错行是文件的最后一行,而不是引号所在的行:

app:
  msg: "hello
  port: 1
while scanning a quoted scalar
 in 'reader', line 2, column 8:
      msg: "hello
           ^
found unexpected end of stream
 in 'reader', line 4, column 1:
    
    ^

可以看到第一段指向第 2 行的引号起点,第二段指向文件末尾。看到 found unexpected end of stream 就去找没闭合的引号。

两种引号的区别需要记住:双引号里反斜杠是转义符,单引号里不是。Windows 路径放在双引号里会触发错误:

app:
  dir: "C:\temp\x"
while scanning a double-quoted scalar
 in 'reader', line 2, column 8:
      dir: "C:\temp\x"
           ^
expected escape sequence of 2 hexadecimal numbers, but found: "

 in 'reader', line 2, column 18:
      dir: "C:\temp\x"
                     ^

用单引号或者不加引号即可,下面这样写,值就是原样的 C:\temp\x:

app:
  dir: 'C:\temp\x'

单引号字符串里要表示一个单引号,写两个:'it''s' 得到 it's;双引号字符串里表示双引号用 \":"say \"hi\"" 得到 say "hi"。我用 PyYAML 验证过这两个例子的结果。

八、方括号、花括号与重复键

行内写法 [a, b] 少了右括号时,报错会落在下一行:

app:
  hosts: [a, b
  port: 1
while parsing a flow sequence
 in 'reader', line 2, column 10:
      hosts: [a, b
             ^
expected ',' or ']', but got :
 in 'reader', line 3, column 7:
      port: 1
          ^

重复键是个值得单独说的问题,因为不同解析器的反应完全不同:

app:
  name: a
app:
  port: 1
while constructing a mapping
 in 'reader', line 1, column 1:
    app:
    ^
found duplicate key app
 in 'reader', line 3, column 1:
    app:
    ^

js-yaml 同样报错:duplicated mapping key (3:1)。但 PyYAML 不报错,读出来是 {'app': {'port': 1}}——第一段的 name 悄悄丢失。如果你用 Python 脚本校验配置,要么换成严格模式的库,要么配合 yamllint(它会报 duplication of key "app" in mapping)。

九、不报错,但值被改了:类型陷阱

这是最需要警惕的一类。Spring Boot 的 SnakeYAML 实现 YAML 1.1,规则比较"热心";js-yaml 4 按 YAML 1.2 的核心规则,要保守得多。下面是同一份输入在三个解析器里的实测结果(Spring Boot 3.5.7 / PyYAML 6.0.3 / js-yaml 4.1.0):

写法Spring BootPyYAMLjs-yaml(本站工具)
enabled: yestrueTrue"yes"
mode: offfalseFalse"off"
country: NOfalseFalse"NO"
flag: y"y""y""y"
zip: 0012346686681234
zip: 08"08""08"8
code: 0108810
time: 12:30:454504545045"12:30:45"
ver: 3.103.13.13.1
n: 1e31000.0"1e3"1000
d: 2026-10-20"2026-10-20"date 对象Date 对象
v: ~(或 v: 留空)""(空字符串)Nonenull

几点解读:

解决办法只有一个:想要字符串就加引号。country: "NO"、zip: "001234"、ver: "3.10"。引号只影响类型,不会变成值的一部分。

同一份 YAML,在线工具和 Spring Boot 的结果可能不同。用 YAML ⇄ JSON 看到 "yes",不代表 Spring Boot 里读到的也是字符串。要确保稳妥,写配置时就给歧义值加引号。

十、多行字符串:| 和 >

长文本(SQL、证书、说明文字)不要硬塞在一行里。用 | 保留换行,用 > 把换行折叠成空格:

a: |
  x
  y
b: |-
  x
  y
c: >-
  p
  q

用 PyYAML 解析的结果:a 是 "x\ny\n",b 是 "x\ny",c 是 "p q"。要点:| 和 > 默认会在末尾保留一个换行,后面加 -(即 |-、>-)可以去掉它。块里的缩进必须比键更深,且块内每行缩进要一致。

十一、一套排查流程

  1. 先看行号,再看上一行。报错行附近两三行都要看。
  2. 搜 Tab。在编辑器里搜索正则 \t,有命中就是 Tab 问题。
  3. 找出值里的 : 和 #。它们是"mapping values are not allowed here"最常见的原因。
  4. 二分法缩小范围。把文件前半部分保留、后半部分注释掉,看报错是否消失,几次就能定位。
  5. 用工具对照。把 YAML 粘贴到 YAML ⇄ JSON 工具,右边的 JSON 能直接看出层级和数组长度对不对;Properties 配置则用首页的 Properties ⇄ YAML 转换器,转换结果与预期不符时,往往就是缩进或引号的问题。
  6. 上 lint。命令行可以用 yamllint(pip install yamllint)。我对第二节的 Tab 例子运行它,输出 2:1 error syntax error: found character '\t' that cannot start any token (syntax);对重复键则输出 duplication of key "port" in mapping。它还会对 yes 这类歧义值给出 truthy value should be one of [false, true] 的警告,正好补上"不报错但值不对"的盲区。

配置里嵌套了 JSON 时,也可以用 JSON 格式化 / 校验 先把 JSON 部分单独验证一遍,再贴回 YAML。

读完就动手试试:粘贴你的配置,一键转换并检查结果。

相关文章
YAML 和 Properties 有什么区别?怎么选语法对照表、Spring Boot 优先级、类型陷阱、中文编码,以及什么时候该选哪种。 Spring Boot 配置文件从 properties 迁移到 yaml:步骤与常见坑五步迁移流程、多环境 on-profile 写法,以及 @PropertySource、类型、优先级等实测过的坑。
更多工具
Properties ⇄ YAMLProperties 与 YAML 在线互转 YAML ⇄ JSONYAML 与 JSON 在线互转 JSON 格式化 / 校验格式化、压缩 JSON,语法错误提示行号 Unix 时间戳转换时间戳与日期时间互转,支持毫秒 Base64 编码 / 解码支持中文 UTF-8 的 Base64 互转 URL 编码 / 解码URL 参数编解码,支持中文 Crontab 表达式解析解析定时表达式,预测最近执行时间 我的 IP 查询查看当前公网出口 IP,一键复制