YAML:编码与语义差异

YAML: encoding vs semantic differences

我想更好地了解 YAML 的哪些方面是指数据编码,哪些方面是指语义。

一个简单的例子:

test1: dGVzdDE=
test2: !!binary |
  dGVzdDE=
test3: 
- 116
- 101
- 115
- 116
- 49
test4: test1

这些值中哪些(如果有)是等价的?

我认为 test1 对文字字符串值 dGVzdDE= 进行了编码。 test2test3 都对同一个数组进行编码,只是使用了不同的编码。我不确定 test4,它包含与 test2test3 相同的字节,但这会使它成为等效值还是 YAML 中的 string 不同于字节数组?

不同的工具似乎会产生不同的答案:

{
  "test1": "dGVzdDE=",
  "test2": "dGVzdDE=\n",
  "test3": [
    116,
    101,
    115,
    116,
    49
  ],
  "test4": "test1"
}

YAML 规范的目的是什么?

平等

您要求 等价 但这不是规范中的术语,因此不能讨论(至少不能没有定义)。我将继续讨论 equality,它由规范定义如下:

Two scalars are equal only when their tags and canonical forms are equal character-by-character. Equality of collections is defined recursively.

您示例中的一个节点具有标签 !!binary,但其他节点没有标签。因此,我们必须检查规范对没有 explicit 标签的节点标签的说明:

标签和方案

YAML 规范规定每个节点都有一个 标签。任何没有显式标签的节点都会分配一个 non-specific 标签。节点分为 scalars(从文本创建)和 collections(序列和映射)。每个 non-plain 标量 节点(即引号中的每个标量或通过 |> 给出的每个标量)都没有显式标记得到 non-specific 标签 !,没有显式标签的每个其他节点都获得 non-specific 标签 ?.

在加载期间,规范定义 non-specific 标签将通过使用 方案 解析为特定标签。该规范描述了一些方案,但不需要实现来支持任何特定方案。

故障保护方案,设计为最基本的方案,将解析non-specific标签如下:

  • 在标量上 !!str
  • 关于 !!seq
  • 的序列
  • 映射到 !!map

就是这样。

允许方案通过考虑 non-specific 标签的种类、节点在文档中的位置以及节点的内容,从 non-specific 标签派生出特定标签。例如,JSON 方案 将根据其内容给标量 true 标记 !!bool

规范说 non-specific 标签 ! 应该只解析为标量的 !!str,序列的 !!seq,映射的 !!map ,但不需要这个。这是大多数实现所支持的,意味着如果您引用标量,您 得到一个字符串。这很重要,因此您可以引用标量 "true" 以避免获得布尔值。

顺便说一句,规范并没有说那里定义的每个步骤都要按照规范中的定义来执行,它更像是一种逻辑描述。很多实现实际上并没有从non-specific标签过渡到特定标签,而是直接根据scheme规则为他们加载的YAML数据选择原生类型。

应用平等

现在我们知道了标签是如何分配给节点的,让我们来看看你的例子:

test1: dGVzdDE=
test2: !!binary |
  dGVzdDE=

这两个值立即不相等,因为即使没有标签,它们的内容也不同:文字块标量(由 | 引入)包含最后的换行符,因此 test2 的值是 "dGVzdEDE=\n",因此不等于 test1 值。您可以使用 |- 引入文字标量,而不是切断最后的换行符,我想这是您的意图。在那种情况下,标量内容是相同的。

现在是标签:test1 的值是一个普通标量,因此它有一个 non-specific 标签 ?。现在的问题是:这会被解决为 !!binary 吗?可能有一个方案可以做到这一点,但规范没有定义。但是想一想:如果 看起来像 base64 编码的数据 ,则为每个标量分配标签 !!binary 的方案将是一个非常具体的方案。

至于其他值: test3 值是一个序列,因此显然不等于任何其他值。 test4 值包含其他地方不存在的内容,因此也不相等。

但是 yaml-online-parser 做事!

是的。 YAML 规范明确指出加载 YAML 数据的目标是本机数据类型。 标签 被认为是可以通过特定实现映射到本机数据类型的通用提示。因此,例如 !!str 将解析为目标语言的字符串类型。

这种到本地类型的映射是如何完成的 implementation-defined(而且必须如此,因为规范无法满足所有语言的需求)。 yaml-online-parser 使用 PyYAML,它所做的是将 YAML 加载到 Python 的本机数据类型中,然后再次转储它。在此过程中,!!binary 将被加载到 Python 二进制字符串中。但是,在转储期间,此二进制字符串将被解释为 UTF-8 字符串,然后写入普通标量。你可以说这是一个错误,但它肯定不违反规范(因为规范不知道 Python 二进制字符串是什么,因此没有定义它的表示方式)。

无论如何,这表明一旦您转换到本机类型并再次返回,一切都会顺利进行并且没有什么是确定的,因为本机类型不在规范之内。不同的实现会给你不同的输出,因为它们是被允许的。 !!binary 不是在JSON 方案 所以即使将您的输入翻译成 JSON 也不是 well-defined.

如果您想要一个在线工具来显示规范的 YAML 表示,而无需将数据加载到本机类型并返回,您可以使用 NimYAML testing ground(我的作品)。

结论

两个 YAML 输入是否相等是一个学术问题。由于YAML确实允许不同的方案,所以这个问题只能在特定方案的上下文中明确回答。

但是,您会发现很少有 YAML 规范之外的正式方案定义。大多数使用 YAML 的应用程序将以不太正式的方式记录其输入结构,并且大多数时候不讨论 YAML 标签。这很好,因为如前所述,加载 YAML 不需要直接实现规范中描述的逻辑过程。

出于实际目的,您的答案应该来自使用 YAML 数据的应用程序的文档。如果文档非常好,它会回答这个问题,但是很多 YAML-consuming 应用程序只是使用他们使用的 YAML 实现的默认设置而没有告诉你这个。

所以要点是:了解您的应用程序并了解它使用的 YAML 实现。