It's not the YAML spec's fault, but

It’s not the YAML spec’s fault, but

不是 YAML 规范的错,但是…… 2026 年 9 月 10 日,星期四

So I came across another blog post shitting on YAML again. “The Norway Problem”, the author proudly repeats while posting falsehoods. So I looked into it myself, and here’s what I found.

我又看到一篇抨击 YAML 的博客文章。作者自豪地重复着“挪威问题”(The Norway Problem),同时却在散布谬论。于是我亲自研究了一下,以下是我的发现。

The problems with YAML

YAML 的问题

YAML™ (rhymes with “camel”) is a human-friendly, cross language, Unicode based data serialization language designed around the common native data types of dynamic programming languages. It is broadly useful for programming needs ranging from configuration files to internet messaging to object persistence to data auditing and visualization. Over the years, YAML has become quite popular for a lot of things. Depending on how experienced you are as a programmer, you’ve probably seen it before in config files or whatever. It has, in that time, garnered much criticism.

YAML™(发音与“camel”押韵)是一种对人类友好、跨语言、基于 Unicode 的数据序列化语言,围绕动态编程语言中常见的原生数据类型而设计。它广泛应用于各种编程需求,从配置文件到互联网消息传递,再到对象持久化、数据审计和可视化。多年来,YAML 在许多领域变得非常流行。根据你的编程经验,你可能在配置文件或其他地方见过它。在此期间,它也招致了许多批评。

There’s generally three big issues, and they kind of play into each other in different ways:

  1. The specification is unreasonably huge
  2. Library implementations are all over the place
  3. The implicit typing (“The Norway Problem”)

通常有三个主要问题,它们以不同的方式相互影响:

  1. 规范大得离谱
  2. 库实现参差不齐
  3. 隐式类型转换(即“挪威问题”)

Some of this criticism is valid, but some is misplaced. At least, that was my understanding. Let’s take a look.

其中一些批评是合理的,但有些则找错了对象。至少,我是这么认为的。让我们来看看。

My own experiences with YAML

我使用 YAML 的个人经历

In my years programming, I’ve used YAML, TOML, JSON, and others in many projects. I have some choice words about all of these, but I’ll focus on YAML’s part. The first thing I made that really uses YAML was my custom Python Discord bot, 9 years ago. This was probably my first “major” project that wasn’t contributing to Space Station 131, so I was on my own with much of it. I ended up using YAML for the configuration file. I did not run into any of the commonly-complained about issues with YAML, but I realize this was just luck. I was still using yaml.safe_load() and nothing more.

在我的编程生涯中,我在许多项目中都使用过 YAML、TOML、JSON 等。我对这些格式都有一些看法,但我将重点关注 YAML。我第一个真正使用 YAML 的项目是我 9 年前编写的自定义 Python Discord 机器人。这可能是我第一个非贡献性质的“大型”项目,所以我大部分时间都是独自完成的。最终我选择了 YAML 作为配置文件。我没有遇到任何关于 YAML 的常见抱怨,但我意识到这纯属运气。当时我只是简单地使用了 yaml.safe_load()

This does not mean the usage of YAML was without issues. A silly thing about the way my config file worked was that I actually had two files: config.yml, and override.yml. This was because I always needed config.yml loaded to provide the basic structure and default values, and then merged override.yml into it to create the nested datastructure the Python code would access. But is that YAML’s fault? No, it’s not. I had other serialization issues with the pickle files I was using to persist data. Stupid shit like “defaultdict instances have trouble serializing because they effectively store a lambda.” Nowadays I can look back at this time and realize what the common cause here is: Python. Or, more accurately, dynamic languages in general. It is impossible to correctly do “to/from object” serialization in a dynamic language like Python. Anyways.

但这并不意味着使用 YAML 就没有问题。我的配置文件处理方式有一个愚蠢之处:我实际上有两个文件:config.ymloverride.yml。这是因为我总是需要加载 config.yml 来提供基本结构和默认值,然后将 override.yml 合并进去,以创建 Python 代码所需的嵌套数据结构。但这是 YAML 的错吗?不,不是。我在使用 pickle 文件持久化数据时还遇到了其他序列化问题。比如一些愚蠢的情况,例如“defaultdict 实例难以序列化,因为它们实际上存储了一个 lambda 函数”。如今回顾这段时间,我意识到根本原因在于:Python。或者更准确地说,是动态语言。在像 Python 这样的动态语言中,要正确实现“对象到数据/数据到对象”的序列化是不可能的。总之,继续下一个项目。

Next project. Space Station 14 uses YAML for all “prototypes” (and some other stuff). This means data definitions of entities, recipes, and like 200 other things. Originally the project actually used XML for this, but when I took the reigns I made the decision to switch this to YAML. For the most part, this has been a “great success.” The biggest problem we’ve really had is that new contributors sometimes have a hard time realizing it’s whitespace sensitive and then get silly syntax errors. Annoying, but not the end of the world.

下一个项目。《空间站 14》(Space Station 14)的所有“原型”(以及其他一些内容)都使用 YAML。这意味着实体、配方以及其他约 200 种事物的定义都由它负责。最初该项目实际上使用 XML,但当我接手时,我决定将其切换为 YAML。总的来说,这是一个“巨大的成功”。我们遇到的最大问题是,新贡献者有时难以意识到它对空格敏感,从而导致一些愚蠢的语法错误。虽然烦人,但并非世界末日。

How do we avoid “The Norway Problem”?

我们如何避免“挪威问题”?

Here’s the secret: while the exact implementation details have varied (we’re at “serv3” now, and it could probably be 4 actually), we’ve always done the actual object deserialization logic with our own code. This means that we take the graph objects (YamlMappingNode, YamlScalarNode, etc) from our library, and we parse them ourselves. We don’t deserialize “no” as a boolean when we shouldn’t, because we only do boolean deserialization when we’re reading a field that needs to take a boolean. Shocking.

秘诀在于:虽然具体的实现细节有所不同(我们现在处于“serv3”版本,实际上可能已经是第 4 版了),但我们始终使用自己的代码来处理实际的对象反序列化逻辑。这意味着我们从库中获取图对象(YamlMappingNode, YamlScalarNode 等),然后自己解析它们。我们不会在不该转换时将“no”反序列化为布尔值,因为我们只在读取需要布尔值的字段时才进行布尔反序列化。令人震惊吧。

This, of course, is the correct way to do object serialization. You serialize against a model set in the program code. This is how we avoid nonsense in Space Station 14, and how I could’ve avoided nonsense in my Discord bot. So when I see these posts about how “bad” YAML is because it might deserialize “no” wrong, all I can think of is “gigantic, utter, dynamically typed skill issue.”

当然,这才是进行对象序列化的正确方式。你应该根据程序代码中设定的模型进行序列化。这就是我们在《空间站 14》中避免乱象的方法,也是我本可以在 Discord 机器人中避免乱象的方法。所以,当我看到那些抱怨 YAML 因为可能错误地反序列化“no”而“很糟糕”的文章时,我只能想到:“这是典型的、彻底的、动态类型语言下的技术能力问题。”

But is this really true? What if I’m wrong? What if we’re using YAML wrong, and the spec demands this behavior? Then I’d surely be making a fool of myself! So… let’s check the damn spec! What does the spec say?

但这真的是这样吗?如果我错了呢?如果我们使用 YAML 的方式不对,而规范确实要求这种行为呢?那我肯定会出丑!所以……让我们去查查该死的规范吧!规范是怎么说的?

According to their site, YAML has a couple important revisions: 1.0 (January 2004), 1.1 (January 2005), 1.2 (July 2009). Let’s start taking a look at 1.0 to see what’s up. If you start digging into this, you’ll quickly realize that the spec is light on details about how type conversions and parsing should work. Section 2.4 has this to say:

根据其官网,YAML 有几个重要的修订版本:1.0(2004 年 1 月)、1.1(2005 年 1 月)、1.2(2009 年 7 月)。让我们从 1.0 开始看看情况。如果你深入研究,会很快发现该规范在类型转换和解析应如何工作方面细节很少。第 2.4 节是这样说的:

In YAML, plain (unquoted) scalars are given an implicit type depending on the application. The examples in this specification use types from YAML’s tag repository, which includes types like integers, floating point values, timestamps, null, boolean, and string values.

在 YAML 中,普通(未加引号)标量根据应用程序被赋予隐式类型。本规范中的示例使用了来自 YAML 标签库的类型,其中包括整数、浮点值、时间戳、null、布尔值和字符串值等类型。

Following the link, we get: Following is a description of the three mandatory core tags. YAML requires support for the seq, map and str tags. YAML also provides a set of universal tags, that are not mandatory, in the YAML tag repository available at https://yaml.org/spec/type.html. These tags represent native data types in most programming languages, or are useful in a wide range of applications. Therefore, applications are strongly encouraged to make use of them whenever they are appropriate, in order to improve interoperability between YAML systems.

点击链接后,我们得到: 以下是对三个强制核心标签的描述。YAML 要求支持 seq、map 和 str 标签。YAML 还提供了一组非强制性的通用标签,位于 YAML 标签库(https://yaml.org/spec/type.html)。这些标签代表了大多数编程语言中的原生数据类型,或者在广泛的应用中非常有用。因此,强烈建议应用程序在适当的时候使用它们,以提高 YAML 系统之间的互操作性。

That last link is dead, but it probably pointed to something similar to Integer Language-Independent Type for YAML™ Version 1.1. Looking at the integer type, for example: Resolution and Validation: Valid values must match the following regular expression, which may also be used for implicit tag resolution: [-+]?0b[0-1_]+ # (base 2) |[-+]?0[0-7_]+ # (base 8) |[-+]?(0|[1-9][0-9_]) # (base 10) |[-+]?0x[0-9a-fA-F_]+ # (base 16) |[-+]?[1-9][0-9_](:[0-5]?[0-9])+ # (base 60) If we search more in…*

最后一个链接已经失效,但它可能指向类似于 YAML™ 1.1 版本中与语言无关的整数类型的内容。以整数类型为例: 解析与验证:有效值必须匹配以下正则表达式,该表达式也可用于隐式标签解析:[-+]?0b[0-1_]+ # (二进制) |[-+]?0[0-7_]+ # (八进制) |[-+]?(0|[1-9][0-9_]) # (十进制) |[-+]?0x[0-9a-fA-F_]+ # (十六进制) |[-+]?[1-9][0-9_](:[0-5]?[0-9])+ # (六十进制) 如果我们继续搜索……