Jsonnet 中的 + 运算符和 std.mergePatch 有什么区别?

Kei*_*son 5 merge operators jsonnet

Jsonnetstd.mergePatch实现了RFC7396,但在我幼稚的测试中,我没有发现它的行为方式和+操作符之间有什么不同;例如,+操作符尊重x+语法。std.mergePatch在 Jsonnet本身中实现的,这似乎暗示它与+运算符不同,我假设它是内置的。

这两种合并方式的语义有什么不同?

sba*_*ski 9

Jsonnet 的+std.mergePatch是完全不同的操作。该+运算符仅在单个级别上操作并std.mergePatch递归遍历对象并合并嵌套对象。用一个例子来解释最简单:

local foo = { a: {b1: {c1: 42}}},
      bar = { a: {b2: {c2: 2}}};
foo + bar
Run Code Online (Sandbox Code Playgroud)

输出:

{
   "a": {
      "b2": {
         "c2": 2
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

注意bar.a完全替换了foo.a. 随着+第二对象中的所有领域覆盖的第一个对象的字段。将其与使用的结果进行比较std.mergePatch(foo, bar)

{
   "a": {
      "b1": {
         "c1": 42
      },
      "b2": {
         "c2": 2
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

由于fooandbar都有一个字段a, is 被合并并且最终结果包含b1b2

所以重申一下,+是一个“平面”操作,它用第二个对象的字段覆盖第一个对象的字段。

然而,这并不是故事的结局。您提到了field+: value语法,我将尝试解释它的真正作用。在 Jsonnet+中不仅仅是覆盖,而是面向对象意义上的继承。它创建一个对象,该对象是从第一个对象继承的第二个对象的结果。拥有一个运算符有点奇怪——在所有主流语言中,这种关系都是静态定义的。在 Jsonnet 中,当您这样做时foo + barbar对象可以从foothrough访问内容super

{ a: 2 } + { a_plus_1: super.a + 1}
Run Code Online (Sandbox Code Playgroud)

这导致:

{
   "a": 2,
   "a_plus_1": 3
}
Run Code Online (Sandbox Code Playgroud)

您可以使用此功能合并更深层次的字段:

{ a: {b: {c1: 1}, d: 1}} +
{ a: super.a + {b: {c2: 2} } }
Run Code Online (Sandbox Code Playgroud)

导致:

{
   "a": {
      "b": {
         "c2": 2
      },
      "d": 1
   }
}
Run Code Online (Sandbox Code Playgroud)

不过,这有点重复(如果字段名称更长,会很烦人)。所以我们有一个很好的语法糖:

{ a: {b: {c1: 1} , d: 1}} +
{ a+: {b: {c2: 2}} }
Run Code Online (Sandbox Code Playgroud)

请注意,在这些示例中,我们只对我们选择的一个特定字段进行了合并。我们仍然替换了 的值a.b。这更加灵活,因为在许多情况下,您不能天真地合并内部的所有内容(有时嵌套对象是“原子的”,应该完全替换)。

+:的版本与带有的版本的工作方式相同super。细微的区别在于它+:实际上转换为类似的东西if field in super then super.field + val else val,因此当super根本没有提供或没有此特定字段时,它也会返回相同的值。例如,{a +: {b: 42}}评估到{a: { b: 42 }}.

强制性讲道:虽然+很强大,但请不要滥用它。当您需要参数化某些内容时,请考虑使用函数而不是继承。