YAML?那是挪威问题
摘要
探讨了臭名昭著的 YAML '挪威问题',即国家代码 'NO' 被解析为布尔值 false,追溯其历史并提供如加引号等解决方案。
暂无内容
查看缓存全文
缓存时间: 2026/05/23 00:27
# YAML?那是挪威问题
来源:https://lab174.com/blog/202601-yaml-norway/
< 返回 LAB174.com (https://lab174.com)
## 什么是 YAML
Yaml 是一种以人类可读性著称的数据序列化语言,常用于配置文件与元数据。以下是一个简单示例:
```yaml
# project.yaml
title: Nonoverse
description: Beautiful puzzle game about nonograms.
link: https://lab174.com/nonoverse
countries:
- DE
- FR
- PL
- RO
```
我们来验证上述示例能否正确解析。我们将使用 Python1 (https://lab174.com/blog/202601-yaml-norway/#fn1) 搭配 PyYaml2 (https://lab174.com/blog/202601-yaml-norway/#fn2) 6.0.3 版本(撰写本文时的最新版)。首先安装它:
```bash
python3 -m pip install pyyaml==6.0.3
```
然后编写一个简单的脚本来解析该 yaml 文件:
```python
# python-pyyaml.py
import json
import yaml
with open("project.yaml", "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
print(json.dumps(data, indent=2))
```
运行 `python3 python-pyyaml.py` 产生如下输出:
```json
{
"title": "Nonoverse",
"description": "Beautiful puzzle game about nonograms.",
"link": "https://lab174.com/nonoverse",
"countries": [
"DE",
"FR",
"PL",
"RO"
]
}
```
到目前为止一切正常。当我们修改原始 yaml 文件,将挪威的两位 ISO 国家代码添加到现有列表中:
```yaml
countries:
- DE
- FR
- NO
- PL
- RO
```
使用相同的解析方法,现在该文件产生如下结果:
```json
{
"title": "Nonoverse",
"description": "Beautiful puzzle game about nonograms.",
"link": "https://lab174.com/nonoverse",
"countries": [
"DE",
"FR",
false,
"PL",
"RO"
]
}
```
注意 `NO` 已被替换为 `false`。这出乎意料。上下文没有任何迹象表明这里应该出现布尔值。`NO` 这个字面量位于像 `FR` 或 `PL` 这样的国家代码列表中,形式上也非常相似。问题当然在于 "no" 也是一个英文单词,表示否定含义。该特性最初是为了允许以更人性化的方式书写布尔值而添加的,例如:
```yaml
platforms:
iPhone: yes
iPad: yes
AppleWatch: no
```
解析后会变成:
```json
{
"platforms": {
"iPhone": true,
"iPad": true,
"AppleWatch": false
}
}
```
其初衷是让配置文件读起来像自然语言。但在实践中,这种表现引发了问题,成为了 YAML 中臭名昭著的“挪威问题”。一种解决方法是给字符串加引号,像这样:
```yaml
countries:
- DE
- FR
- "NO"
- PL
- RO
```
加上引号后,文件按预期解析:
```json
{
"title": "Nonoverse",
"description": "Beautiful puzzle game about nonograms.",
"link": "https://lab174.com/nonoverse",
"platforms": {
"iPhone": true,
"iPad": true,
"AppleWatch": false
},
"countries": [
"DE",
"FR",
"NO",
"PL",
"RO"
]
}
```
许多关于 YAML 挪威问题的文章到此为止,将加引号作为标准修复方法。但还有更多内容。
## YAML 的历史
要理解挪威问题今天的状况,我们首先看看 YAML 是如何演变的。
### 2001 年 5 月 – Yaml 首次草案规范
此时 YAML 更多是一个概念而非成熟的语言。它的样子有些不同,但尚可辨认。以下是原始规范的部分示例;完整文档中有更多示例,可惜没有包含布尔值。
```yaml
buyer : %
address : %
city : Royal Oak
line one : 458 Wittigen's Way
line two : Suite 292
postal : 48046
state : MI
family name : Dumars
given name : Chris
```
该文档未提及将 `no` 解析为 `false`。“序列化格式 / BNF”一节甚至包含一个拼写错误和一条“待办”注释3 (https://lab174.com/blog/202601-yaml-norway/#fn3):
> 本节包含 Yaml 语法的 BNF4 (https://lab174.com/blog/202601-yaml-norway/#fn4) 产生式。尚有许多工作要做……
### 2004 年 1 月 – Yaml v1.0 最终草案
该版本描述了标量的各种表示方式5 (https://lab174.com/blog/202601-yaml-norway/#fn5),包括带引号的标量和具有隐式类型的纯标量。这正是我们要关注的。v1.0 仅将 `sequence`、`map` 和 `string` 定义为强制类型6 (https://lab174.com/blog/202601-yaml-norway/#fn6)。其余类型是可选的,但存在一份参考规范。该布尔类型的可选参考规范包含了英文单词格式。支持的单词有:`true/false`、`on/off` 以及 `yes/no`7 (https://lab174.com/blog/202601-yaml-norway/#fn7)。这就允许了挪威问题的出现——即使遵循参考规范的该部分被描述为可选的。
——额外:隐式类型可以被显式标签覆盖——我们稍后会讨论。
——额外:单个符号字符,即 `+` 和 `-` 也应被当作 `true` 和 `false`;更甚者,它们被描述为规范形式8 (https://lab174.com/blog/202601-yaml-norway/#fn8)!
### 2005 年 1 月 – Yaml v1.1 最终草案
v1.1 保持了与 v1.0 相同的隐式类型行为。然而,规范中列出的类型(包括布尔)虽然仍非强制,但已强烈推荐9 (https://lab174.com/blog/202601-yaml-norway/#fn9)。
——额外:单个符号字符不再包含,规范形式变为 `y/n`10 (https://lab174.com/blog/202601-yaml-norway/#fn10)。
### 2009 年 7 月 – Yaml 修订版 1.2.0
其目标是使 YAML 与 JSON 兼容,甚至允许 JSON 成为 YAML 的子集11 (https://lab174.com/blog/202601-yaml-norway/#fn11)。
**隐式类型规则已被移除,包括布尔英文单词格式。**
——额外:显式类型规则仍然存在。
理论上,挪威问题不应再存在,至少从这个 YAML 修订版开始。那么,为什么在 2026 年我们仍然能看到它?
### Yaml 规范版本历史至 v1.2.0
| Yaml 规范版本 | 日期 | `no` 的类型 | `no` 的值 |
|---------------|------|-------------|-----------|
| 首次草案规范 | 2001年5月 | 未指定 | 未指定 |
| v1.0 | 2004年1月 | 布尔 | `false` |
| v1.1 | 2005年1月 | 布尔 | `false` |
| v1.2.0 | 2009年7月 | 字符串 | `"no"` |
表 1:Yaml 规范变更摘要。注意“`no` 的类型”和“`no` 的值”标签指的是不带引号的字面量。
## YAML 的实际使用
要理解挪威问题为何持续存在,我们需要审视实现 YAML 规范变更所涉及的工作范围。前面的文本中已有些许线索:我们看到 YAML 支持隐式类型、显式类型以及各种表示形式。此外,不同 YAML 规范版本发布之间的时间间隔以年为单位。字里行间隐藏的是:YAML 及其规范非常、极其、**极度**复杂。说真的,怎么强调都不过分。自 v1.0 起,YAML 的目标就是建立在 XML12 (https://lab174.com/blog/202601-yaml-norway/#fn12) 以及其他一些技术之上,如最终草案中所列13 (https://lab174.com/blog/202601-yaml-norway/#fn13):
> YAML 集成并建立在由 C、Java、Perl、Python、Ruby、RFC0822(邮件)、RFC1866(HTML)、RFC2045(MIME)、RFC2396(URI)、XML、SAX 和 SOAP 所描述的概念之上。
YAML 支持附件、自定义标签、引用——列表还在继续。甚至还有 YAXML,一种用于 YAML 的 XML 绑定14 (https://lab174.com/blog/202601-yaml-norway/#fn14)。有 9 种书写多行字符串的方式——有人声称实际数量是 6315 (https://lab174.com/blog/202601-yaml-norway/#fn15)。像 `?`、`!`、`!!` 这样的字符在某些情况下具有特殊含义,后者甚至允许任意代码执行。
鉴于这种复杂性,挪威问题并非 YAML v1.1 中唯一的语言怪癖。修订版 v1.2 简化了布尔行为以及更多(例如 null 和数值的处理),而其他语言特性保持不变。库是如何应对如此复杂规范的变化的呢?
## YAML 库
截至 2026 年 1 月,流行的 YAML 库仍然没有从 v1.1 迁移到 v1.2,它们仍然表现出挪威问题。一些较小的替代项目已经出现,但其使用量尚未超过现有的 v1.1 库。一些用户构建了自己的替代解析器,混合了 v1.1 和 v1.2 的功能,或者专注于适合其需求的 YAML 子集。以下是一些例子。
### PyYaml
如前所述,PyYaml 是 Python 最流行的 YAML 库,也是整体上最流行的 Python 库之一。PyYaml 从未添加 v1.2 支持。PyYaml 的 GitHub 项目中有一个自 2017 年就有的开放议题,要求引入 v1.2 支持16 (https://lab174.com/blog/202601-yaml-norway/#fn16)。至少还有另外两个相关的开放议题,以及几个已关闭的议题。存在一个非官方库17 (https://lab174.com/blog/202601-yaml-norway/#fn17),可以在 PyYaml 之上使用以提供部分 v1.2 支持(其文档指出并非所有 v1.2 功能都已实现)。另一个 Python 库 ruamel.yaml18 (https://lab174.com/blog/202601-yaml-norway/#fn18) 默认支持 v1.2。
### LibYaml
LibYaml 是长期存在的 C YAML 库,被广泛用作其他工具和绑定的依赖项。与 PyYaml 一样,它也是一个“官方”实现——从某种意义上说,其规范仓库托管在 GitHub 上,由官方‘yaml’ GitHub 账号所有。LibYaml 也从未添加 v1.2 支持。LibYaml 的 GitHub 项目中有一个自 2016 年就有的开放议题,请求添加 v1.2 支持19 (https://lab174.com/blog/202601-yaml-norway/#fn19)。如前所述,LibYaml 位于依赖树的深处;改变其行为风险尤其高且速度缓慢。一个不那么流行的库 libfyaml20 (https://lab174.com/blog/202601-yaml-norway/#fn20) 默认支持 v1.2。
### Golang 的 gopkg.in/yaml.v3
目前处于无人维护状态21 (https://lab174.com/blog/202601-yaml-norway/#fn21),历史上最流行,且仍在 GitHub Stars 上领先于其他 Golang YAML 库。它特别有趣,因为它声明支持 v1.1 和 v1.2 的混合22 (https://lab174.com/blog/202601-yaml-norway/#fn22)。Golang 最流行的活跃维护库23 (https://lab174.com/blog/202601-yaml-norway/#fn23) 默认采用 v1.2 行为。
### Kyaml
Kyaml 是为 Kubernetes 项目构建的 YAML 方言,于 2025 年 6 月启动。其目标是提供更安全、更少歧义的工具;它也是专门为 Kubernetes 设计的,用通用性换取了可预测性。公告博文直接引用了挪威问题24 (https://lab174.com/blog/202601-yaml-norway/#fn24)。
## 挪威问题解决了吗?
YAML 生态系统不仅包括库,还包括用户社区。其中包括:关于 YAML 总体以及挪威问题特别强烈的、相互冲突的观点。在某种程度上,这种结果是可以预见的;毕竟 YAML 非常流行、狡诈地复杂,并且被用于不同类型的场景,从小型个人配置文件到关键基础设施设置。许多文本根本不分清 YAML 规范版本25 (https://lab174.com/blog/202601-yaml-norway/#fn25)。即使使用了规范版本号,也经常出现拼写错误。不难找到文档声称隐式布尔类型是 YAML 规范版本 1.2 的特性26 (https://lab174.com/blog/202601-yaml-norway/#fn26)(正确的版本是 v1.1);错误会被发现27 (https://lab174.com/blog/202601-yaml-norway/#fn27)并最终更新,但这需要比最初打字错误更多的时间和精力。
另一方面,我们看到一些用户宣称挪威问题已经解决,因为它不存在于最新规范版本中,或者因为他们自己从未经历过,或者其他原因28 (https://lab174.com/blog/202601-yaml-norway/#fn28)。公平地说,那个语言特性在十多年前就被移除了,而流行的库仍然支持旧版规范,这确实出乎意料。技术上,该问题在规范中已经解决——但实际上,正如我们所见,大多数广泛采用的实现仍然支持隐式布尔类型。
最后,还有一些最终用户对 YAML 非常不满,以至于他们宁愿选择几乎任何其他东西29 (https://lab174.com/blog/202601-yaml-norway/#fn29)。我们最终面对无数的使用场景(爱好、专业、关键基础设施……)、角色(规范作者、库维护者、晚上 11 点调试部署失败的最终用户……),以及同样多的观点。
## 下一步是什么?
在 YAML 最终草案 v1.0 中,规范规定,除了 `yes` 和 `no` 之外,`+` 和 `-` 也应被解析为布尔值。这在 v1.1 中被移除。曾有一个想法,当加号或减号前面带有点号(`.+` 和 `.-`)时保留该功能,但未能流行起来。
尽管存在众所周知和不太为人所知的怪癖,YAML 仍然流行且广泛使用。在这种规模下,小怪癖会级联成意外问题。而变更——或修复——以冰川般的速度引入。话又说回来,YAML 的魅力有其位置,其流行程度就是证明。虽然规范变更的采用非常缓慢,但它仍在进行中。新项目可能会采用更新的库,其中挪威问题不再存在。如果本文有一个单一要点,那就是:YAML 生态系统是碎片化的;整体上它正在向一个稍微更严格的版本迈进。隐式布尔类型正在被移除,它不再出现在官方规范中,大多数新库都遵守这一点。然而截至 2026 年 1 月,较老的库仍停留在旧版规范上,它们仍然更流行,更新或淘汰它们可能需要一段时间。
## 常见问题
### 为什么不直接用 JSON 替代 YAML?
一个常见的回答是“没有注释”——因为 JSON 不支持注释30 (https://lab174.com/blog/202601-yaml-norway/#fn30);YAML 的许多其他功能也不被支持。这使得 JSON 成为更简单、更严格的替代方案。它是否更适合你的项目,取决于项目本身。一如既往,个人偏好也起一定作用。注意:JSON 有其自己的变体,比如 JSONC31 (https://lab174.com/blog/202601-yaml-norway/#fn31)。
### YAML 是 JSON 的超集吗?
在写完本文之后,我仍然不完全确定。尽管 YAML 修订版 1.2.0 的目标就是实现这一点,并且修订版 1.2.0 和 1.2.1 都明确声明过32 (https://lab174.com/blog/202601-yaml-norway/#fn32):
> 因此,YAML 可以被视为 JSON 的自然超集,提供了更好的可读性和更完整的信息模型。
该文本已从最新的 YAML 修订版 1.2.2 中移除。一篇流行的文章33 (https://lab174.com/blog/202601-yaml-norway/#fn33) 声称证明 YAML 不是 JSON 的超集,但那篇文章使用了 v1.1 解析器——而我们知道 v1.1 从未声称与 JSON 兼容。所以这没有帮助。实际原因可能是 YAML 要求映射具有唯一键34 (https://lab174.com/blog/202601-yaml-norway/#fn34),而 JSON 仅推荐这样做35 (https://lab174.com/blog/202601-yaml-norway/#fn35)。因此,也许大多数 JSON(即对象具有唯一键的 JSON)是 YAML 的一个子集。仍然存在一些歧义。
### 哪里出了问题?
这个问题超出了本文的范围——这里的目标是将事实置于“如果……会怎样?”之上。如果我必须回答,我会说没有哪里出了问题。当一个具有稳定生态系统的复杂技术引入破坏性变更时,有时这个过程可能需要很长时间。这里的主要惊喜是 YAML 到底有多复杂。另外,正如我们所见,由于 YAML 和相关工具是自由软件,任何人都可以为提高 v1.2 的采用率做出贡献——或者迁移到更适合自己的工具,甚至创建一个。
### 那 TOML、六十进制数、模式、人类基因、Ruby 或 Perl 呢?
这些主题与挪威问题的关联不大,而且本文已经够长了。如果你喜欢阅读,请在某个地方留下正面反馈,第二部分可能会出现。同时,请访问我的主页36 (https://lab174.com/blog/202601-yaml-norway/#fn36) 并查看我的其他项目——也许你会找到其他喜欢的东西。
## 后记
隐式布尔类型已被移除,但显式布尔
相似文章
YAML:问题一大堆
一篇讽刺文章,指出YAML在DevOps和编程环境中的各种缺陷和怪癖,例如解析错误和Kubernetes等工具中的配置错误。
为YAML辩护
Posit的一篇博文为YAML辩护,反驳当前普遍认为TOML更优的共识,回顾了配置格式的历史,并指出YAML的规范与工具已经演进,解决了过去的批评。
这不是YAML规范的错,但是
这篇文章讨论了对YAML的常见批评,比如其庞大的规范、不一致的库实现以及隐式类型问题,并分享了作者在项目中的YAML使用个人经历。
你的JSON在欺骗你
本文解释了JavaScript中JSON序列化如何悄悄改变数据:大整数失去精度,undefined属性消失,Date变成字符串,NaN变成null。文章追溯了JSON的历史,并主张开发者应定义明确的传输格式,以避免意外的数据契约。
在 Rust 中“尊重原格式”地修补 YAML
本文评估了多款 Rust 库,旨在找出能够在编程修改 YAML 文件时保留原始格式与注释的最佳工具。