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?
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.