D
开发工具箱

YAML 解析陷阱:那些让你踩坑的缩进和类型推断

数据格式 2026年6月15日 约 1 分钟阅读

什么是 YAML?

YAML 是一种面向人类阅读和编写的数据序列化语言。它的设计信条很简单:配置文件是给人看的,不是给机器看的。YAML 是 JSON 的严格超集——任何合法的 JSON 都是合法的 YAML,但反过来不行,YAML 多了很多 JSON 没有的花活。

用缩进表示层级、用冒号分隔键值、用短横线表示列表——乍一看比 JSON 干净得多。没有花括号,没有引号,没有逗号。干净是真的干净,但坑也是真的多。

我第一次认真写 YAML 是在配 GitHub Actions 的时候。看着文档照猫画虎写了个 workflow,提交上去 CI 直接红了,报错信息含糊得一批。反复检查了半小时,最后发现是多写了一个空格——缩进层级错了一格。从那之后我就对 YAML 的缩进有了肌肉记忆般的恐惧。

YAML 的工作原理

YAML 解析器的核心逻辑分两步:先把文本切成 token(缩进、标量、映射、序列),再根据缩进深度构建树状结构。

缩进就是结构

YAML 用空格(不是 Tab,Tab 在 YAML 里是非法字符)的缩进来表示层级关系:

parent:
  child1: value1
  child2:
    grandchild: value2

解析器数空格,2 格缩进代表子级,4 格代表孙级。这套逻辑翻译成 JSON 就是:

{
  "parent": {
    "child1": "value1",
    "child2": {
      "grandchild": "value2"
    }
  }
}

隐式类型推断——YAML 最恐怖的特性

YAML 跟 JSON 最大、也是最危险的区别在于:它会自动猜你的数据类型。JSON 里 "true" 是字符串,true 是布尔值,泾渭分明。YAML 里你写 true,解析器自动给你一个布尔值;写 123,给你一个整数;写 2023-01-15,给你一个日期对象。

听起来挺贴心的?往下看你就知道这有多致命了。

核心特性

特性说明
人类友好无括号、无引号、无逗号,缩进即结构
注释支持# 写注释,JSON 不支持
引用和锚点& 定义锚点,* 引用,避免重复定义
多行字符串`
隐式类型自动推断布尔值、整数、浮点数、日期、null
JSON 超集任何合法 JSON 都可用 YAML 解析器正常解析

注释是我觉得 YAML 对 JSON 唯一的碾压级优势。你没法在一个 .json 配置文件里解释某个字段为什么是这个值——JSON 里写注释是语法错误。而 YAML 里随手加个 # 这里选 8080 是因为 80 端口被 nginx 占了,过三个月你回来看自己配的配置文件也不会一头雾水。

实际应用场景

1. CI/CD 流水线配置

GitHub Actions、GitLab CI、CircleCI 全都用 YAML 定义 workflow。我之前写 CI/CD pipeline 时,在一个步骤里把环境变量的值写成了 NO——打算表示”不启用某个特性”。结果 YAML 1.1 解析器把 NO 自动推断成了布尔值 false,程序读到环境变量的值是 false 而不是字符串 "NO",行为完全跑偏。这就是著名的 “Norway 问题”

2. Docker Compose 和 Kubernetes

Docker Compose 的 docker-compose.yml、Kubernetes 的各种资源清单,全是 YAML。K8s 的 deployment 配置动辄上百行,嵌套四五层,缩进稍有差池整个 apply 就直接报错。

有一次我在配 Kubernetes deployment,把环境变量里的 version: 1.10 写进 YAML,K8s 的应用读到的是 1.1——因为 YAML 把 1.10 当成了浮点数,直接把末尾的零给抹了。字符串当数字解析,小数点后的精度直接蒸发了,你能不崩溃?

3. Ansible 自动化

Ansible 的 playbook 和 inventory 文件都基于 YAML。用 | 操作符写多行 shell 脚本片段特别方便——相比 JSON 里那堆 \n 转义,YAML 的写法确实清晰一个数量级。

4. 各类框架配置文件

Ruby on Rails 的数据库配置、Jekyll 的前置元数据、Home Assistant 的智能家居自动化规则——全是 YAML。说白了,凡是开发者需要手写的配置,大概率是 YAML。

常见误区与陷阱

陷阱一:挪威问题的幽灵——隐式类型推断

这是一个写入计算机民俗的经典 bug。在 YAML 1.1 规范中,以下字符串都会被自动转成布尔值:

true, True, TRUE  → true
false, False, FALSE → false
yes, Yes, YES  → true
no, No, NO  → false
on, On, ON  → true
off, Off, OFF → false

所以如果你有一个国家列表配置文件:

countries:
  - US
  - UK
  - NO   # 挪威的 ISO 代码,被解析成了 false!

挪威直接被 YAML 从地图上抹掉了。解决方法很简单但很烦:不确定类型的值一律加引号 "NO"'NO'。我们这个工具的 YAML 转 JSON 功能底层使用了 YAML 1.2 规范的解析器,但默认会用引号包裹所有标量值来规避这个问题——你可以关掉这个安全策略,但我建议你别关。

陷阱二:多行字符串的 | 和 >

YAML 提供了两种多行字符串操作符,很多新手分不清。

  • |(literal block scalar):保留换行符。你写几行,输出的字符串里就有几个 \n
  • >(folded block scalar):把多行折叠为一行,换行符变成空格。有多余空行才会保留换行。
script: |
  npm install
  npm run build
  npm test

这个会保留为三行命令。而:

description: >
  这是一段很长的描述文字,
  它在 YAML 里换行了,
  但解析后会变成一行。

解析结果是 这是一段很长的描述文字,它在 YAML 里换行了,但解析后会变成一行。。我的经验是:shell 脚本用 |,长篇描述用 >,不确定就自己在脑子里跑一遍解析器的逻辑再决定。

陷阱三:六十进制数

YAML 1.1 支持六十进制(sexagesimal)数,用冒号分隔——看起来就跟时间一样:

duration: 1:30:00

这会被解析成整数 5400(1×3600 + 30×60 + 0)。如果你的配置里恰好有类似时间格式的字符串,比如视频时长的标签 length: 2:15,那你会拿到整数 135 而不是字符串 "2:15"。更坑的是——1:2 不会触发六十进制解析(因为不够三部分),但 1:02:00 会。YAML 2.0 规范里这个特性已经被移除了,但很多旧解析器还在用 1.1 规范。

陷阱四:缩进不可见

YAML 的缩进是语义的一部分,但它用空格——人类肉眼分辨 2 个空格和 4 个空格的能力远不如分辨 {}。一个缩进错误可能完全改变数据的层级结构,而且不像 JSON 那样会报语法错误——YAML 解析器会默默地给你一个结构完全不同的结果。

YAML vs JSON vs TOML

YAMLJSONTOML
人类可读性极高,但有陷阱中,括号多
注释支持不支持支持
类型推断有(危险)有(较安全)
缩进敏感性极高
工具链成熟度极高
主要用途CI/CD、K8s、配置Web API、数据交换配置文件(Rust/Python)

JSON 是机器对机器的协议,没有注释、没有推断、没有歧义——它呆板,但呆板在这种场景下是优点。YAML 是人写配置的选择,灵活但陷阱多。TOML 像是两者的折中——有注释、缩进不敏感、类型推断相对克制,Rust 和 Python 生态里用得越来越多。

我个人现在对配置文件的偏好是:API 交互绝对是 JSON,手写配置优先 TOML,只有项目生态强依赖 YAML(比如 K8s、Ansible)的时候才用 YAML。被 YAML 坑了太多次之后,你会对它的”便捷”产生一种斯德哥尔摩综合征般的复杂感情。

常见问题

Q: YAML 到 JSON 的转换安全吗?

转换本身安全,真正不安全的是你 YAML 源文件里的隐式类型。一个 "NO" 字符串在 YAML 里可能已经被解析成了布尔值,再转 JSON 就变成 false 而不是 "NO"。所以转换之前,把你 YAML 里所有不该被推断类型的值都用引号括起来。我们这个工具提供了”引号包裹所有字符串”的选项,建议开着。

Q: YAML 里用 2 空格还是 4 空格缩进?

2 空格是 YAML 社区的约定俗成,绝大多数官方示例都用 2 格。你非要用 4 格也行,但在同一个文件里绝对不能混用——解析器会用缩进差值算层级,混用等于自掘坟墓。

Q: 怎么把 JSON 转回 YAML?

反向转换同样有坑。JSON 的 null 转 YAML 会是 null(字符串),但 YAML 里 ~ 和空值也代表 null。如果你想干净地转换,我们有 JSON 转 YAML 工具,它会处理好这些映射细节。

Q: YAML 的锚点和引用转了 JSON 会怎样?

锚点 & 和引用 * 在转换为 JSON 时会被展开为实际值。JSON 不支持锚点语法,所以引用会被原地替换。如果你 YAML 文件里用锚点是为了减少重复,转 JSON 后文件会变大——这是设计使然,不是 bug。

Q: 为什么我的 CI/CD 配置在本地能跑、push 上去就报错?

十有八九是缩进问题。GitHub Actions 等 CI 平台的 YAML 解析器和本地的可能不同——版本、实现、严格程度都有差异。建议在 push 之前用在线 YAML 校验工具或 IDE 插件做一次语法检查,比事后对着红色 build log 抓狂好得多。