TOML与YAML与StrictYAML比较

71

TOML表示:“TOML和YAML都强调人类可读性特征,例如注释使得更容易理解给定行的用途。然而,TOML通过结合这些要素来实现,允许注释(不像JSON)同时保持简单(不像YAML)。”

我发现TOML除了不依赖于重要的空白以外,对于它所声称的“简单性”我不太确定。那是什么意思?

接着我看到StrictYAML,“StrictYAML是一种类型安全的YAML解析器,解析并验证YAML规范的受限子集。” “类型安全”是什么意思(再次)?TOML没有解决的问题是什么,而StrictYAML认为他解决了?我确实阅读了StrictYAML网站上的文章,但仍然不清楚。

因此,TOML和StrictYAML都想解决YAML存在的“问题”。但除了缩进之外,还有什么问题呢?

---- 更新 ----

我在Reddit上找到了StrictYaml的作者,他讨论了YAML和TOML的区别(YAML vs TOML)。但目前为止我得到的答案是“strictyaml对YAML的理解相当差”,然而https://github.com/crdoconnor/strictyaml截至2021年12月28日已经获得了957个star。所以我有点迷茫,不知道该使用哪个,最终我还是选择了YAML,因为我的大部分YAML都很简单。

YAML缺点:

隐式类型转换会导致意外类型变化(例如,在原来有字符串的地方放置3,它会神奇地变成整数)。

一堆令人讨厌的“隐藏功能”,如节点锚点和引用,使其看起来不清晰(尽管公平地说,很多人不使用这些功能)。

TOML缺点:

语法更繁琐(特别是对于多行字符串)。

数组/表格的方式令人困惑,尤其是表格中的数组。

我编写了一个库,删除了我不喜欢的大部分YAML中的讨厌的东西,只保留了我喜欢的核心部分。它与其他一堆配置格式进行了相当详细的比较,例如:https://hitchdev.com/strictyaml/why-not/toml/


4
自从一年前我第一次提出这个问题以来,我对TOML有了更深入的了解。对我而言,它是一个按部分(他们称之为表)分组的键值对配置文件。对于小型简单项目来说,它很好用。但是对于大型项目,我时不时会看到一些批评。 - Qiulang
3个回答

111

这可能是一个有偏见的答案,因为我编写了多个YAML实现。


替代方案中解决YAML常见批评的问题

YAML杰出的语义特性在于它可以表示可能是循环图的数据结构。此外,YAML映射可以使用复杂节点(序列或映射)作为键。当您想要表示任意数据结构时,这些功能就是您潜在需要的。

另一个奇特的YAML功能是标签。它们的目标是抽象化不同编程语言中的不同类型,例如!!map在Python中将是dict,但在JavaScript中将是object。虽然很少明确使用,但隐式标记解析通常是false被加载为布尔值而droggeljug被加载为字符串的原因。这里的明显目标是通过不要求编写像!!bool false这样的布尔值或在每个字符串值上强制使用引号来减少噪音。

然而,现实情况表明,许多人对此感到困惑,并且YAML定义的将yes解析为布尔值的做法也没有帮助。 YAML 1.2试图通过描述您可以使用的不同模式来纠正这一点,其中基本的“failsafe”模式仅加载到映射、序列和字符串,而更复杂的“JSON”和“core”模式则进行了额外的类型猜测。然而,大多数YAML实现(特别是PyYAML)长时间以来都停留在YAML 1.1版本(许多实现最初是重写的PyYAML代码,例如libyaml,SnakeYAML)。这巩固了YAML做出可疑输入决策需要修复的观点。

现在,一些实现已经改进,您可以使用failsafe模式避免不需要的布尔值。在这方面,StrictYAML限制自己使用failsafe模式;不要相信它声称这是PyYAML无法做到的新颖之处。

YAML实现的常见安全问题是它们将标记映射到任意构造函数调用上(你可以在这里阅读有关基于此的Ruby on Rails漏洞的信息)。请注意,这不是YAML的缺陷;YAML没有建议在对象构造期间调用未知函数。这里的根本问题在于数据序列化与数据封装的对立;如果您的编程语言仅提供构造函数作为构造对象的唯一方法,则反序列化数据时必须执行此操作。解决方法只是调用已知的构造函数,在一系列此类漏洞(例如使用SnakeYAML的另一个漏洞)浮出水面后得到广泛实施。现在,要调用未知构造函数,您需要在PyYAML中使用一个名为DangerLoader的类。

TOML

TOML的主要语义差异在于它不支持循环、复杂键或标记。这意味着虽然您可以将YAML加载到任意用户定义的类中,但始终会将TOML加载到包含数据的表格数组中。

例如,虽然YAML允许您将{foo: 1, bar: 2}加载到一个具有foobar整数字段的类的对象中,但TOML始终会将其加载到表中。在文档中通常可以找到YAML功能的杰出示例是它可以将标量1d6加载到对象{number: 1, sides: 6}中;TOML始终将其作为字符串"1d6"加载。
TOML在这里被认为很简单,因为它不做一些YAML所做的事情。例如,如果您正在使用像Java这样的静态类型语言,将{foo: 1, bar: 2}加载到对象myObject中后,您可以安全地访问myObject.foo(获取整数1)。如果您使用了TOML,则需要执行myObject["foo"],如果键不存在,可能会引发异常。在像Python这样的脚本语言中,这种情况就不太成立:在这里,myObject.foo编译并在运行时错误时失败,如果foo恰好不是myObject的属性。
我从回答很多关于YAML的问题中得出的看法是,人们不使用YAML的功能,并且通常将所有内容加载到Map<String, Object>这样的结构中,然后从那里继续。如果您这样做,您也可以使用TOML。
TOML提供了一种不同类型的简单性,即其语法:由于它比YAML简单得多,因此更容易发出用户可以理解的错误。例如,在YAML语法错误中常见的错误文本是“在此上下文中不允许映射值”(请尝试在SO上搜索此内容以找到大量问题)。例如,在此处:
foo: 1
  bar: 2

错误信息不能帮助用户修复错误。这是因为YAML语法复杂:YAML认为1bar是多行标量的一部分(因为bar:的缩进大于foo:),将它们放在一起,然后看到第二个:并失败,因为多行标量不能用作隐式键。然而,很可能用户只是将bar:缩进或以为可以同时给foo(1)和一些子元素一个标量值。由于YAML语法的可能性,编写有助于用户的错误消息将是困难的。
由于TOML的语法更简单,错误消息更容易理解。如果编写TOML的用户不需要具备解析语法的背景,则这是一个巨大的优势。
TOML在概念上优于YAML:由于其结构允许的自由度较小,因此阅读起来更容易。阅读TOML时,您总是知道,“好的,我将嵌套表格并在其中放置值”,而使用YAML时,则存在某种任意结构。我认为这需要更多的认知负荷来读取YAML文件。
StrictYAML认为它提供了类型安全性,但由于YAML不是编程语言,并且明确不支持赋值,因此根据StrictYAML链接的Wikipedia定义,这种说法毫无意义(类型安全性随您使用的编程语言而来去;例如,在将任何YAML加载到适当的Java类实例中之后,任何YAML都是类型安全的,但在像Python这样的语言中永远不会是类型安全的)。通过查看其删除功能列表,它显示了对YAML相当贫乏的理解:
- 隐式类型转换:可以在使用failsafe模式的YAML实现中停用,如上所述。 - 对象的直接表示:它只是链接到Ruby on Rails事件,暗示这是不可避免的,即使大多数实现今天都没有删除该功能也是安全的。 - 禁止重复键:YAML规范已经要求这样做。 - 节点锚点和引用:StrictYAML认为使用此功能进行去重对非程序员来说不可读,忽略了其意图是能够序列化循环结构,而没有锚点和别名是不可能的。
在反序列化方面,
- 所有数据都是字符串、列表或OrderedDict。
这基本上是TOML支持的相同结构(我认为StrictYAML支持映射中的复杂键,因为listOrderedDict在Python中都不可哈希)。
您还失去了将数据反序列化为预定义类结构的能力。有人可能会认为,无法构造具有明确定义字段的类对象使StrictYAML比标准YAML不够类型安全:标准YAML实现可以保证返回的对象具有由类型描述的某种结构,而StrictYAML在每个级别上都给你一个字符串、列表或OrderedDict,你不能做任何限制。
虽然它的一些论点是有缺陷的,但生成的语言仍然可用。例如,使用StrictYAML,您不需要担心billion laughs attack困扰一些YAML实现。同样,这不是YAML的问题,而是实现的问题,因为YAML不要求实现复制一个在多个位置上被锚定和引用的节点。
底线
相当多的YAML问题源于糟糕的实现,而不是语言本身的问题。然而,作为一种语言,YAML确实很复杂,语法错误可能很难理解,这可能是使用像TOML这样的语言的一个有效理由。至于StrictYAML,它确实提供了一些好处,但我建议不要使用它,因为它没有适当的规范,只有一个实现,这是一个非常容易成为维护噩梦的设置(项目可能会停止,容易出现破坏性变化)。

1
您好,我看到您在回答其他YAML相关问题时,比如https://dev59.com/DFgQ5IYBdhLWcg3wxGzs。您是否认为相对于TOML,缩进是YAML的一个弱点? - Qiulang
6
我认为缩进用于结构化是有趣但不完美的概念(不仅适用于YAML,还适用于Python或Nim)。如果没有一个可见的标记来结束一个结构,那么随着嵌套和长度的增加,代码阅读会变得更加困难。虽然在Python中,您可以重新组织代码以解决过度嵌套的问题,但在YAML中这并不容易实现。不过,类似的论点也被用于比较C语言的“}”(与Pascal语言的“end <name>;”相比),而“}”仍然被大多数现代语言使用,因此它似乎是个人偏好。 - flyx
3
至少 TOML 没有这个问题。也许这是其“简单性”的原因之一? - Qiulang
2
@Qiulang 我认为对于深度嵌套的结构,TOML 不是很好,因为每次都需要给出完整的表路径,这使得理解数据结构变得困难。你可以缩进代码来解决这个问题,但是完全展开的路径会严重违反 DRY 原则。如果你不喜欢显著缩进,你仍然可以使用带有流语法的 YAML;如果 YAML 有一个选项来禁止块(基于缩进)语法,那通常会是更好的选择。 - flyx
1
TOML中的嵌套确实是一个尚未解决的问题,例如,请参见有关此问题的讨论以及我在此处提供的TOML和YAML示例:https://github.com/toml-lang/toml/issues/781#issuecomment-1004397808 - mcarans
显示剩余2条评论

23

StrictYAML类型安全性 -- "挪威问题"

这是StrictYAML团队提供的一个例子:

countries:
- FR
- DE
- NO

隐式地将“NO”翻译为False值。

>>> from pyyaml import load
>>> load(the_configuration)
{'countries': ['FR', 'DE', False]}

https://hitchdev.com/strictyaml/why/implicit-typing-removed


1
谢谢你澄清了这个问题!也许你能对TOML提些评价?:)我得到的另一个回答说它不好。 - Qiulang
8
那个 load 调用的实际输出还包括 <stdin>:1: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details.。如警告所述,您可以轻松执行 load(the_configuration, Loader=yaml.BaseLoader)避免这种情况。该网页中链接的页面上有详细说明。这将解决 StrictYAML 页面中描述的所有问题。 - flyx
1
然而,safe_load(the_configuration) 仍然存在本答案中描述的问题,并且没有产生任何警告。 - Keldorn

0
YAML非常复杂,比大多数人用作配置/数据交换语言的需求要复杂得多。这种复杂性不仅会导致输入错误,正如前面提到的那样,甚至还可能引发安全漏洞。是的,YAML甚至可以在加载它的编程语言中实例化对象!
因此,许多库都实现了“安全加载器”,但并不是所有库都默认启用。你必须选择加入。:-/
StrictYaml和TOML都试图驯服这种复杂性。StrictYaml通过删除90%的功能来实现这一点,包括所有有问题的功能。它只返回字符串,并且您必须定义一个模式以进行正确的输出类型。TOML则使用类似ini的语法,并且一开始就不支持这些功能。
总的来说,StrictYaml更强大且仍然安全。然而,对于较大的文档,您可能需要一个具备可折叠缩进指南的能力强大的编辑器。
对于小到中等大小的文件,TOML也是可以的,但它处于简单的.ini文件和功能强大的StrictYaml之间的尴尬位置。

谢谢你的回答,但我不明白我们如何从中得出“StrictYaml更强大”的结论。 - Qiulang
它支持验证架构。此外,层次数据语法更加明显,因此不太受限制。对于较大的文件来说,它更强大,更好用。 - Gringo Suave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接