jsonnet

Cycle through all keys in JSONNET, when the keys are not yet known


My question has two parts, first I will show the input and the desired output:

INPUT

{
  "outside": {
    "entry1": {
      "t": [
        "c1",
        "c2"
      ]
    },
    "entry2": {
      "t": [
        "c1",
        "d1",
        "d2"
      ]
    }
  },
  "outside2": "irrelevant"
}

OUTPUT

{
  "outside": {
    "entry1": {
      "t": [
        "c1",
        "c2",
        "d1",
        "d2"
      ]
    },
    "entry2": {
      "t": [
        "c1",
        "c2",
        "d1",
        "d2"
      ]
    }
  },
  "outside2": "irrelevant"
}

The difficulty is I do not know if "entry" will be "somethingelse", so it could be outside has two attributes, somethingelse1 and somethingelse2, or entry3 and entry500, etc. I cannot hardcode the field names.

I am dividing this into two subtasks, the first is to cycle through all fields, that is my question, how can I do this basic task in JSONNET.

Open to ideas for the second part, which is merging the two t's (always "t" for this attribute). Already thinking to use std.setUnion, so my question is really the first part, how do I iterate through attributes that have names not known until later.

I tried using x for x in etc.outside.

EDIT: As a followup, trying to actually take entry2.t as an empty array, and the merge actually needs to occur so that each entry will merge itself with a default, so for example here entry2 should be empty and the default would be [ "c1", "d1", "d2" ]


Solution

  • Please read https://jsonnet.org/ref/stdlib.html, there are several functions available to loop over an object: std.objectFields(), std.objectValues(), std.objectKeysValues().

    Below example answers your question on how to implement it, mind it's a very rough 1st-shot approach (not optimized, neither uses recursion, etc), tho serves to actually show the usage of the three functions I mention above

    source: foo-input.json

    {
      "outside": {
        "entry1": {
          "t": [
            "c1",
            "c2"
          ]
        },
        "entry2": {
          "t": [
            "c1",
            "d1",
            "d2"
          ]
        }
      },
      "outside2": "irrelevant"
    }
    

    source: foo.jsonnet

    local input = import 'foo-input.json';
    
    local mergeEntries(obj, fixedKey) = {
      // NOTE: below assumes that the `fixedKey` field is an array
      local merged = std.foldl(
        function(acc, x) acc { [fixedKey]+: std.get(x, fixedKey, []) },
        std.objectValues(obj),
        {},
      ),
      local uniqMerged = merged { [fixedKey]: std.set(merged[fixedKey]) },
    
      // Emit the merged object content, keeping original keys
      [entryKey]: uniqMerged
      for entryKey in std.objectFields(obj)
    };
    
    {
      [kv.key]:
        // if the content of the outside entry is an object, merge its `fixedKey` field
        if std.isObject(kv.value)
        then mergeEntries(kv.value, 't')
        else kv.value
      // Loop over outside key,values
      for kv in std.objectKeysValues(input)
    }
    
    

    output

    $ jsonnet foo.jsonnet
    {
       "outside": {
          "entry1": {
             "t": [
                "c1",
                "c2",
                "d1",
                "d2"
             ]
          },
          "entry2": {
             "t": [
                "c1",
                "c2",
                "d1",
                "d2"
             ]
          }
       },
       "outside2": "irrelevant"
    }