1

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 
}
Heiko Finzel
  • 78
  • 1
  • 8

3 Answers3

2

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

sbarzowski
  • 2,707
  • 1
  • 20
  • 25
  • I think that the whole point from @Heiko Finzel is that «The reason it works is that : is "default visibility" which takes the visibility of the field it overrides.» _doesn't_ seem to be the case here, running your example with go-jsonnet v0.17.0 indeed _shows_ the hidden field also, note that it behaves as expected if overridden with a "non-dynamic" construct. – jjo Oct 31 '21 at 00:09
  • 1
    «running your example with go-jsonnet v0.17.0 indeed shows the hidden field also» Hmmm... I seems to work as espected ("visible" was visible, "hidden" was hidden) when I run it. Did you run the exact snippet from my answer? – sbarzowski Oct 31 '21 at 00:29
  • yup, verbatim pasted, session "log" at https://gist.github.com/jjo/db3fb25330dde66e6c8e848e8fdc107e – jjo Oct 31 '21 at 12:55
  • 1
    WTbF!, just realized that `brew install jsonnet` actually installed cpp-jsonnet which *is* showing this issue, installing `go-jaonnet` instead does the right thing (!) – jjo Oct 31 '21 at 13:00
0

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 
}
jjo
  • 2,595
  • 1
  • 8
  • 16
  • Didn't seem to fix my problem. If I use std.objectFieldsAll: `stack + stack2` is a noop, `stack2 + stack` will reset my changes. If I use std.objectFields: I was sure it should solve my problem, but didn't for reasons I don't understand. – Heiko Finzel Nov 03 '21 at 15:08
0

@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

Heiko Finzel
  • 78
  • 1
  • 8