Merge Jsonnet and JSON

1. Problem with binary + operator

Merging Jsonnet and JSON with + doesn't recursively merge objects because there is no way for the JSON to use the +: syntax sugar.

For example, evaluating the following Jsonnet:

local jsonnet = { a: { b: 2, z: 26 } };
local json = std.parseJson('{ "a": { "c": 3 } }');

jsonnet + json

Produces the JSON:

{
   "a": {
      "c": 3
   }
}

2. Problem with std.mergePatch

Because std.mergePatch adheres to RFC 7396, it doesn't respect hidden Jsonnet fields.

For example, evaluating the following Jsonnet:

local jsonnet = { visible: true, hidden:: true };
local json = std.parseJson('{ "additional": true }');

local merged = std.mergePatch(jsonnet, json);

{
  local visible_fields = std.objectFields(merged),
  hidden_fields: std.setDiff(std.objectFieldsAll(merged), visible_fields),
  visible_fields: visible_fields,
}

Produces the JSON (note the absent "hidden" field):

{
   "hidden_fields": [ ],
   "visible_fields": [
      "additional",
      "visible"
   ]
}

3. Defining mergePatchAll

Semantics of std.mergePatch but including hidden fields.

local mergePatchAll(target, patch) =
  if std.isObject(patch) then
    local target_object =
      if std.isObject(target) then target else {};
    local visible_target_fields =
      if std.isObject(target_object) then std.objectFields(target_object) else [];
    local hidden_target_fields =
      if std.isObject(target_object) then std.setDiff(std.objectFieldsAll(target_object), visible_target_fields) else [];

    local patch_fields = std.objectFields(patch);
    local patch_null_fields = [k for k in patch_fields if patch[k] == null];
    local visible_target_fields_also_in_patch = std.setUnion(visible_target_fields, patch_fields);
    local hidden_target_fields_also_in_patch = std.setUnion(hidden_target_fields, patch_fields);

    std.foldr(
      function(k, acc)
        acc +
        if !std.objectHas(patch, k) then
          { [k]:: target_object[k] }
        else if !std.objectHasAll(target_object, k) then
          { [k]: $.mergePatchAll(null, patch[k]) tailstrict }
        else
          { [k]: $.mergePatchAll(target_object[k], patch[k]) tailstrict }
      , std.setDiff(hidden_target_fields_also_in_patch, patch_null_fields), {
        [k]:
          if !std.objectHas(patch, k) then
            target_object[k]
          else if !std.objectHas(target_object, k) then
            $.mergePatchAll(null, patch[k]) tailstrict
          else
            $.mergePatchAll(target_object[k], patch[k]) tailstrict
        for k in std.setDiff(visible_target_fields_also_in_patch, patch_null_fields)
      }
    )
  else patch;

For example, evaluating the following Jsonnet:

<<mergePatchAll>>
local jsonnet = { visible: true, hidden:: true };
local json = std.parseJson('{ "additional": true }');

local merged = mergePatchAll(jsonnet, json);

{
  local visible_fields = std.objectFields(merged),
  hidden_fields: std.setDiff(std.objectFieldsAll(merged), visible_fields),
  visible_fields: visible_fields,
}

Produces the JSON:

{
   "hidden_fields": [
      "hidden"
   ],
   "visible_fields": [
      "additional",
      "visible"
   ]
}