Heiko Finzel
Heiko Finzel

Reputation: 118

how to do proper for-loops in jsonnet?

Each time I think I finally understood jsonnet, it comes around to hit me in the face ... -.-

I have something like the following:

local applyModifications(kp) = {
  [topLvlKey]: {
    [subKey]: myfunction(kp[topLvlKey][subKey])
    for subKey in std.objectFieldsAll(kp[topLvlKey])
  },
  for topLvlKey in std.objectFieldsAll(kp)
};

I want to iterate over everything inside the first 2 levels of an object an apply some function there ...

Bascially that works ... But depending on if I use std.objectFieldsAll or std.objectFields, hidden fields are visible afterwards or missing completely.

How would/could I do this without touching the hidden "property"? I understand my problem is, that I use a object-comprehension here and (to refer to an error message) that those "Object comprehensions cannot have hidden fields"... But as far as I understand jsonnet, something-comprehensions are the only way to create for-loops, right?

Testcode:

// vim: set ts=2 sw=2 expandtab :
local myfunction(o) = o {
  spec+: {
    foo: 'bar'
  }
};

local applyModifications(kp) = {
  [topLvlKey]: {
    [subKey]: myfunction(kp[topLvlKey][subKey])
    for subKey in std.objectFieldsAll(kp[topLvlKey])
  },
  for topLvlKey in std.objectFieldsAll(kp)
};    

local stack = {
  fooService: {
    fooResource: {
      kind: 'PrometheusRule',
      spec: {
        groups: [
          { name: 'fooGroup', rules: [{ alert: 'fooAlert', expr: 'fooExpr' }] },
          { name: 'barGroup', rules: [{ alert: 'fooAlert', expr: 'fooExpr' }] },
        ],
      },
    },
  },
  fooService2:: {
    fooResource: {
      kind: 'PrometheusRule',
      spec: {
        groups: [
          { name: 'fooGroup', rules: [{ alert: 'fooAlert', expr: 'fooExpr' }] },
          { name: 'barGroup', rules: [{ alert: 'fooAlert', expr: 'fooExpr' }] },
        ],
      },
    },
  },
};

local stack2 = applyModifications(stack);
   
{
  modified: stack2 
}

Upvotes: 2

Views: 10999

Answers (3)

Heiko Finzel
Heiko Finzel

Reputation: 118

@sbarzowski :

Took me some time to undestand the difference and adopt the solution to my example (2 loops), but here is what I got and it seems to work:

local applyModifications(kp) = kp + {
  [topLvlKey]: kp[topLvlKey] + {
    [subKey]: myfunction(kp[topLvlKey][subKey])
    for subKey in std.objectFieldsAll(kp[topLvlKey])
  },
  for topLvlKey in std.objectFieldsAll(kp)
};

PS: I'm using go jsonnet v0.17.0

Upvotes: 0

jjo
jjo

Reputation: 3020

Following @sbarzowski comment (full lesson I'd rather say) above, and btw being sure that you're using go-jsonnet fwiw (brew install go-jsonnet on macos), you can trick the visibility "merging" by modifying you last line of code as:

[...]

local stack2 = applyModifications(stack);
   
{
  modified: stack + stack2 
}

Upvotes: 0

sbarzowski
sbarzowski

Reputation: 2991

You can achieve what you want with inheritance.

local applyModifications(obj, f) =
    obj + {
        [x] : f(obj[x]) for x in std.objectFieldsAll(obj)
    }
;

applyModifications({
    visible: "foo",
    hidden:: "bar",
}, function(x) x + " modified")

This is single level for clarity, but it should be straightforward create a two level version (if you have any trouble, let me know).

The reason it works is that : is "default visibility" which takes the visibility of the field it overrides. (You also have force-visible fields with ::).

That said, you're in awkward territory and it can usually be avoided. Objects in Jsonnet replace both objects (struct / class instances) and maps (dicts) from other languages. Even though both concepts are unified, OOP features don't always play nicely with data-structure features.

Usually you want to think of each object as either:

  • A data object, keep all the fields visible and avoid self/super. You can process the fields in aggregate easily.
  • An OOP object or struct. You can use self and super, have hidden fields etc. You handle each field manually as each can have completely different meaning and behavior.

It's expected to have data objects contain OOP objects and vice versa. The awkwardness ensues when one object is in some middle ground.

But as far as I understand jsonnet, something-comprehensions are the only way to create for-loops, right?

Comprehensions are not special. Don't think of them as "for loops" from imperative languages. Array comprehensions are basically a syntax sugar for std.map and std.filter (with arbitrary nesting).

Upvotes: 2

Related Questions