Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between the + operator and std.mergePatch in Jsonnet?

Jsonnet's std.mergePatch implements RFC7396, but in my naive testing I didn't find a different between the way it behaved and the + operator; e.g. the + operator respects x+ syntax. std.mergePatch is implemented in Jsonnet itself, which seems to imply that it is different than the + operator, which I'm assuming is a builtin.

What is different about the semantics of these two ways of merging?

like image 416
Keith Pinson Avatar asked May 06 '20 19:05

Keith Pinson


1 Answers

Jsonnet's + and std.mergePatch are completely different operations. The + operator operates only on a single level and std.mergePatch traverses the object recursively and merges the nested objects. It's easiest to explain with an example:

local foo = { a: {b1: {c1: 42}}},
      bar = { a: {b2: {c2: 2}}};
foo + bar

Output:

{
   "a": {
      "b2": {
         "c2": 2
      }
   }
}

Note that the bar.a completely replaced foo.a. With + all fields in the second object override the fields in the first object. Compare that with the result of using std.mergePatch(foo, bar).

{
   "a": {
      "b1": {
         "c1": 42
      },
      "b2": {
         "c2": 2
      }
   }
}

Since both foo and bar have a field a, is is merged and the final results contains both b1 and b2.

So to reiterate, + is a "flat" operation which overrides the fields of the first object with the fields of the second object.

This is not the end of the story, though. You mentioned field+: value syntax and I will try to explain what it really does. In Jsonnet + is not just overwriting, but inheritance in OO sense. It creates an object which is the result of the second object inheriting from the first one. It's a bit exotic to have an operator for that – in all mainstream languages such relationships are statically defined. In Jsonnet, when you do foo + bar, the bar object has access to stuff from foo through super:

{ a: 2 } + { a_plus_1: super.a + 1}

This results in:

{
   "a": 2,
   "a_plus_1": 3
}

You can use this feature to merge the fields deeper inside:

{ a: {b: {c1: 1}, d: 1}} +
{ a: super.a + {b: {c2: 2} } }

Resulting in:

{
   "a": {
      "b": {
         "c2": 2
      },
      "d": 1
   }
}

This is a bit repetitive, though (it would be annoying if the field name was longer). So we have a nice syntax sugar for that:

{ a: {b: {c1: 1} , d: 1}} +
{ a+: {b: {c2: 2}} }

Please note that in these examples we only did the merging for one particular field we chose. We still replaced the value of a.b. This is much more flexible, because in many cases you can't just naively merge all stuff inside (sometimes a nested object is "atomic" and should be replaced completely).

The version in +: works in the same way as the version with super. The subtle difference is that +: actually translates to something like if field in super then super.field + val else val, so it also returns the same value when super is not provided at all or doesn't have this particular field. For example {a +: {b: 42}} evaluates just fine to {a: { b: 42 }}.

Mandatory sermon: while + is very powerful, please don't abuse it. Consider using using functions instead of inheritance when you need to parameterize something.

like image 147
sbarzowski Avatar answered Nov 17 '22 02:11

sbarzowski