YAML:编码与语义差异
YAML: encoding vs semantic differences
我想更好地了解 YAML 的哪些方面是指数据编码,哪些方面是指语义。
一个简单的例子:
test1: dGVzdDE=
test2: !!binary |
dGVzdDE=
test3:
- 116
- 101
- 115
- 116
- 49
test4: test1
这些值中哪些(如果有)是等价的?
我认为 test1
对文字字符串值 dGVzdDE=
进行了编码。 test2
和 test3
都对同一个数组进行编码,只是使用了不同的编码。我不确定 test4
,它包含与 test2
和 test3
相同的字节,但这会使它成为等效值还是 YAML 中的 string
不同于字节数组?
不同的工具似乎会产生不同的答案:
- https://onlineyamltools.com/convert-yaml-to-json表示
test2
和test3
是等价的,但不同于test4
- https://yaml-online-parser.appspot.com/表示
test2
和test4
是等价的,但不同于test4
- 到
yq
所有条目都不同yq < test.yml
:
{
"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 实现。
我想更好地了解 YAML 的哪些方面是指数据编码,哪些方面是指语义。
一个简单的例子:
test1: dGVzdDE=
test2: !!binary |
dGVzdDE=
test3:
- 116
- 101
- 115
- 116
- 49
test4: test1
这些值中哪些(如果有)是等价的?
我认为 test1
对文字字符串值 dGVzdDE=
进行了编码。 test2
和 test3
都对同一个数组进行编码,只是使用了不同的编码。我不确定 test4
,它包含与 test2
和 test3
相同的字节,但这会使它成为等效值还是 YAML 中的 string
不同于字节数组?
不同的工具似乎会产生不同的答案:
- https://onlineyamltools.com/convert-yaml-to-json表示
test2
和test3
是等价的,但不同于test4
- https://yaml-online-parser.appspot.com/表示
test2
和test4
是等价的,但不同于test4
- 到
yq
所有条目都不同yq < test.yml
:
{
"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 实现。