这可能是一个固执己见的答案,因为我已经编写了多个 YAML 实现。
替代方案解决的对 YAML 的共同批评
YAML 突出的语义特征是它可以表示一个可能的循环图。此外,YAML 映射可以使用复杂的节点(序列或映射)作为键。当您想要表示任意数据结构时,您可能需要这些功能。
另一个奇特的 YAML 功能是标签。他们的目标是对不同编程语言中的不同类型进行抽象,例如,a在 Python中!!map
是 a ,而在 JavaScript 中是 a。虽然很少显式使用,但隐式标记解析是为什么通常作为布尔值加载而作为字符串加载的原因。这里的明显目标是通过不需要在每个字符串值上写入布尔值或强制引号来减少噪音。dict
object
false
droggeljug
!!bool false
然而,现实表明很多人对此感到困惑,YAML 定义yes
可能被解析为布尔值也无济于事。YAML 1.2 试图通过描述您可以使用的不同模式来解决这个问题,其中基本的“故障安全”模式专门加载到映射、序列和字符串,而更复杂的“JSON”和“核心”模式会进行额外的类型猜测。然而,大多数 YAML 实现,主要是 PyYAML,在 YAML 1.1 上保留了很长时间(许多实现最初是重写的 PyYAML 代码,例如 libyaml、SnakeYAML)。这巩固了 YAML 做出需要修复的有问题的打字决定的观点。
如今,一些实现得到了改进,您可以使用故障安全模式来避免不需要的布尔值。在这方面,StrictYAML 将自己限制在故障安全模式;不要相信它的论点,即这是 PyYAML 无法做到的一些新奇事物。
YAML 实现的一个常见安全问题是它们将标签映射到任意构造函数调用(您可以在此处阅读有关 Ruby on Rails 中的漏洞利用)。请注意,这不是 YAML 的缺点;YAML 不建议在任何地方的对象构造期间调用未知函数。这里的基本问题是数据序列化是数据封装的敌人;如果您的编程语言提供构造函数作为构造对象的唯一方法,那么这就是您在反序列化数据时需要做的事情。这里的补救措施只是调用已知的构造函数,在一系列此类漏洞(另一个使用 SnakeYAML iirc)浮出水面后,它被广泛实施。如今,要调用未知的构造函数,您需要使用DangerLoader
在 PyYAML 中恰当命名的类。
TOML
TOML 的主要语义区别在于它不支持循环、复杂键或标签。这意味着虽然您可以在任意用户定义的类中加载 YAML,但您始终将 TOML 加载到包含数据的表或数组中。
例如,虽然 YAML 允许您加载{foo: 1, bar: 2}
到具有整数字段的类的对象中foo
,bar
但 TOML 将始终将其加载到表中。您通常在文档中发现 YAML 功能的一个突出示例是它可以将标量加载1d6
到对象中{number: 1, sides: 6}
;TOML 将始终将其加载为 string "1d6"
。
TOML 在这里的简单性在于它不做 YAML 所做的一些事情。例如,如果您使用像 Java 这样的静态类型语言,则在加载{foo: 1, bar: 2}
到 object之后myObject
,您可以myObject.foo
安全地访问(获取 integer 1
)。如果您使用 TOML,则需要myObject["foo"],
在密钥不存在时引发异常。这在像 Python 这样的脚本语言中不太正确:在这里,myObject.foo
如果foo
不碰巧是myObject.
我在这里回答很多 YAML 问题的观点是,人们不使用 YAML 的功能,并且经常将所有内容加载到类似的结构Map<String, Object>
中(以 Java 为例)并从那里获取。如果你这样做,你也可以使用 TOML。
TOML 提供了另一种简单的语法:由于它比 YAML 简单得多,因此更容易发出用户可以理解的错误。例如,YAML 语法错误中的一个常见错误文本是“在此上下文中不允许映射值”(尝试在 SO 上搜索以找到大量问题)。例如,您可以在这里得到:
foo: 1
bar: 2
错误消息不能帮助用户修复错误。这是因为 YAML 的复杂语法:YAML 认为1
并且bar
是多行标量的一部分(因为bar:
缩进多于foo:
),将它们放在一起,然后看到第二个:
并失败,因为多行标量可能不会用作隐式键。然而,最有可能的是,用户要么只是缩进,要么bar:
认为他们可以同时给 foo ( 1
) 和一些孩子一个标量值。由于 YAML 语法的可能性,编写可以帮助用户的错误消息会很困难。
由于 TOML 的语法更简单,错误信息更容易理解。如果不期望编写 TOML 的用户是具有语法分析背景的人,这是一个很大的优势。
TOML 比 YAML 有一个概念上的优势:由于它的结构允许较少的自由度,它往往更容易阅读。阅读 TOML 时,你总是知道,“好吧,我将有嵌套表,其中包含值”,而使用 YAML,你有一些任意结构。我相信这在读取 YAML 文件时需要更多的认知负担。
严格的 YAML
StrictYAML 争辩说它提供了类型安全,但由于 YAML 不是一种编程语言并且特别不支持赋值,因此根据StrictYAML 链接的Wikipedia 定义,这种说法没有任何意义(类型安全随之而来)使用您使用的编程语言;例如,任何 YAML 在将其加载到适当的 Java 类实例后都是类型安全的,但在 Python 之类的语言中您永远不会是类型安全的)。浏览它的已删除特性列表,它显示出对 YAML 的理解相当差:
- 隐式输入:可以在 YAML 实现中使用故障安全模式停用,如上所述。
- 对象的直接表示:它只是链接到 Ruby on Rails 事件,这意味着这是无法避免的,尽管今天大多数实现都是安全的,而无需删除该功能。
- 不允许重复键:YAML 规范已经要求这样做。
- 节点锚点和引用:StrictYAML 认为使用它进行重复数据删除对于非程序员来说是不可读的,忽略了其意图是能够序列化循环结构,而没有锚点和别名是不可能的。
在反序列化方面,
所有数据都是字符串、列表或 OrderedDict
它与 TOML 支持的结构基本相同(我相信 StrictYAML 支持映射中的复杂键,因为在 Python 中既不支持也不可散列)list
。OrderedDict
您还将失去反序列化为预定义类结构的能力。有人可能会争辩说,无法构造具有明确定义的字段的类对象使得 StrictYAML的类型安全性低于标准 YAML:标准 YAML 实现可以保证返回的对象具有由类型描述的特定结构,而 StrictYAML 为您提供了所有水平一个字符串、一个列表或一个 OrderedDict 并且你不能做任何事情来限制它。
虽然它的一些论点是有缺陷的,但由此产生的语言仍然是可用的。例如,使用 StrictYAML,您无需关心在某些 YAML 实现中出现的十亿次笑声攻击。同样,这不是 YAML 问题,而是实现问题,因为 YAML 不需要实现来复制从多个位置锚定和引用的节点。
底线
相当多的 YAML 问题源于糟糕的实现,而不是语言本身的问题。然而,YAML 作为一种语言肯定是复杂的,语法错误可能很难理解,这可能是使用 TOML 之类的语言的正当理由。至于 StrictYAML,它确实提供了一些好处,但我建议不要使用它,因为它没有适当的规范并且只有一个实现,这是一个很容易成为维护噩梦的设置(项目可能会停止,破坏很容易改变)。