If conditional in jsonnet

7,296 views
Skip to first unread message

Nikhil Sharma

unread,
Jul 28, 2020, 11:33:11 PM7/28/20
to Jsonnet
Hi there,

I'm having an issue with using an if conditional within a long json string constant
```
...
if true then
  "media" : {
     "contents": [
       {
        "url": content.url
       }
        for content in contents
       ]
      },
...
```

basically I'd like to show/hide the entire media object in this json string if the variable is true/false but I'm getting various errors as it seems that the `if` is not being recognised as a valid conditional. I'm using golang. Thanks!

stanislaw...@gmail.com

unread,
Jul 29, 2020, 5:11:39 AM7/29/20
to Jsonnet
This is not a valid place to put a conditional. Your "media object" is a field in some outer object. That outer object definition consists of field definitions, not expressions.

You can include a field conditionally by having its field name evaluate to null when it should be excluded. You can also have a field value evaluate to null – then the field will be present, but it will have null value.

{
  [if false then "media-missing"]: {
    foo: "bar"
  },
  [if true then "media-true"]: {
    foo: "bar"
  },
  "media-null": if false then {
    foo: "bar"
  }
}

Evaluates to:

{
  "media-null": null,
  "media-true": {
    "foo": "bar"
  }
}

Note: the field name expressions are scoped "outside" of object – they don't have access to self, super or object locals. See also: https://jsonnet.org/ref/language.html#field-name-expressions.

Brett Viren

unread,
Jul 29, 2020, 9:35:19 AM7/29/20
to Jsonnet
Hi,

"stanislaw...@gmail.com" <stanislaw...@gmail.com> writes:

> {
> [if false then "media-missing"]: {
> foo: "bar"
> },
> }

I like this "null field name" trick. I'll start to use it. Thanks!

I've been conditionally adding fields using a little function:

local objif(key, val) = if std.type(val)=="null" then {} else {[key]:val};

I call it tacked onto an object being constructed, eg:

local myfunc(a, opt=null) = { a:a } + objif("opt",opt);

This is a bit more verbose and non-DRY than I'd like, but not too bad.

Of course, one can simply leave the "null" field value in place and
apply std.prune() to the resulting object:

local myfunc(a, opt=null) = std.prune({ a:a, opt:opt });

However, I find that when there are many "little" std.prune() calls the
compilation time suffers (with C++ Jsonnet). A single "big" std.prune()
at top level is plenty fast but I want to take care of the nulls
immediately where they are created, thus the "objif()" kludge.

-Brett.

signature.asc

Brett Viren

unread,
Aug 26, 2020, 10:46:37 AM8/26/20
to stanislaw...@gmail.com, Jsonnet
Hi,

"stanislaw...@gmail.com" <stanislaw...@gmail.com> writes:

> {
> [if false then "media-missing"]: {
> foo: "bar"
> },
> [if true then "media-true"]: {
> foo: "bar"
> },
> "media-null": if false then {
> foo: "bar"
> }
> }

Playing with this, it seems like the set of the object's keys are
constructed *before* the object's scope. In particular, inside a
conditional key definition, one may not use another key/value item from
the object, nor an object-local variable. Values defined outside the
scope of the object may be used inside conditional key definitions.

Is this a Jsonnet language feature or is it dependent on the particular
Jsonnet interpreter?

Some examples:

```
$ jsonnet --version
Jsonnet commandline interpreter v0.13.0

$ cat junk.jsonnet
{
as: {deps:[1,2]},
[if std.objectHas($.as, "deps") then "deps"]: $.as.deps,
}

$ jsonnet junk.jsonnet
STATIC ERROR: junk.jsonnet:3:23: No top-level object found.

$ cat junk.jsonnet
{
local as = {deps:[1,2]},
[if std.objectHas(as, "deps") then "deps"]: as.deps,
}

$ jsonnet junk.jsonnet
STATIC ERROR: junk.jsonnet:3:23-25: Unknown variable: as
```

A "global local" is usable:

```
$ cat junk.jsonnet
local as = {deps:[1,2]};
{
[if std.objectHas(as, "deps") then "deps"]: as.deps,
}
$ jsonnet junk.jsonnet
{
"deps": [
1,
2
]
}
```

Cheers,
-Brett.
signature.asc

stanislaw...@gmail.com

unread,
Aug 26, 2020, 3:19:53 PM8/26/20
to Jsonnet
It's like this by design. So, yes, the field expressions are scoped "outside" or "before" an object. I even mentioned this in my original message. 

This way we avoid chicken-and-egg problem. To access object fields (and object locals have access to self and super), you need to know which fields are available. So it's necessary to be able to evaluate all of the field names without referring to fields of the object. 

Potentially, in the future, we could make it possible to refer to object locals as long as they don't use self and super (we would either check that statically or produce a runtime error if self and super are used in such context). That technically doesn't give any extra power over defining these locals outside the object, but might be syntactically nicer.

BTW Jsonnet interpreters are supposed to be consistent, so if there were any differences regarding stuff like scoping rules, it would be considered a bug. We have a formal spec which ensures that.

Brett Viren

unread,
Aug 26, 2020, 3:48:49 PM8/26/20
to stanislaw...@gmail.com, Jsonnet
"stanislaw...@gmail.com" <stanislaw...@gmail.com> writes:

> It's like this by design. So, yes, the field expressions are scoped
> "outside" or "before" an object. I even mentioned this in my original
> message.

Thanks for the explanation and sorry for missing it first time around.

I found one easy solution is to use an extra outer scope:

```
{
local as = {deps:[1,2]},
res: {
[if std.objectHas(as, "deps") then "deps"]: as.deps,
}
}.res
```

Gives:

```
signature.asc
Reply all
Reply to author
Forward
0 new messages