Jsonnet: STATIC ERROR: t.jsonnet:6:12: No top-level object found.

Created on 9 May 2019  路  10Comments  路  Source: google/jsonnet

$ cat t.jsonnet
{
  fields:: ['a'],
} +
{
  [x]: {}
  for x in $.fields
}

I expected the above code to output this:

{
   "a": { }
}

But instead I got this error:

$ jsonnet t.jsonnet
STATIC ERROR: t.jsonnet:6:12: No top-level object found.

I believe I'm using the c++ implementation:

$ jsonnet --version
Jsonnet commandline interpreter v0.12.1

All 10 comments

I confirmed this works with the go implementation.

$ which jsonnet
/Users/jaybuff/go/bin/jsonnet

$ jsonnet t.jsonnet
{
   "a": { }
}

$ jsonnet --version
Jsonnet commandline interpreter v0.12.1

It seems there is some confusion about semantics of $. It doesn't refer to the result of the whole file, but to _lexically_ outermost object.

So for example:

local a = {
  x: "a",
  y: $.x
};
local b = {
  x: "b",
  y: $.x
};
{
  a: a,
  b: b,
} 

results in:

local a = {
  x: "a",
  y: $.x
};
local b = {
  x: "b",
  y: $.x
};
{
  a: a,
  b: b,
} 

So basically it's like every time there are curly braces in your code which are not inside another pair of curly braces it wrapped it like that local $ = { ... }; $ (modulo $ not being an ordinarily valid identifier and slight difference in behavior with comprehensions).

The comprehension is your example is not inside any other object, and when calculating field names the object is not available yet, no self-reference is legal at that stage, so there is no $. Perhaps the error should be more like no outermost object found, since top-level may suggest the value of the whole file.

I got the same behavior for both go-jsonnet and cpp-jsonnet (error No top-level object found.) and it seems correct. Could you check again the go-jsonnet result and provide me with the commit hash? I guess from the path that it was installed with go get, so it could be some intermediate version actually.

BTW to implement something like your example you just need to name the first object explicitly and refer to it. Like for example:

local obj = {
  fields:: ['a'],
};
obj + {
  [x]: {}
  for x in obj.fields
}

or you could wrap it in a function:

local obj_with_fields(obj) = obj + {
  [x]: {}
  for x in obj.fields
};
obj_with_fields({ fields:: ['a'] })

Thank you for the detailed explanation. I was wrong to say it works with the go impl. I don't believe I ran the test correctly.

I understand this now. You're saying this is valid:

{
  fields:: ['a'],
} +
{
  foo: {
    [x]: {
    }
    for x in $.fields
  },
}

and this is not:

{
  fields:: ['a'],
} +
{
  [x]: {}
  for x in $.fields
}

I find that very confusing!

It's like not being able to use this in a constructor. The problem is the object isn't fully baked yet, so you can't access its fields.

I tried your suggestion, @sbarzowski, of creating an intermediate object. The trouble is it doesn't allow me to implement templates with overrides.

How can I write something like this:

local defaults = {
  applications:: ['a'],
  replicas:: 10,
};

local apps =
  defaults + {
    [x]: {
      replicas: $.replicas,
    }
    for x in defaults.applications
  };


apps {
  replicas:: 20,
  applications:: ['b'],
}

That will output

{
   "b": {
      "replicas": 20
   }
}

There are two things to know about Jsonnet objects:

  • They are immutable, + (or implicit inheritance) creates a new objects and local doesn't create anything new that wouldn't be there, it just gives a name to a thing.
  • When object is created, its field _names_ must be known beforehand, but fields _values_ are calculated later.

So it's impossible to have defaultsObj + comprehension + specificConfig in which specificConfig changes what fields comprehension adds - because the comprehension object needs to be created before that with all its fields known. You can however get a pretty similar result by putting the comprehension in a field (then the field names are known and it's the value of the field that changes depending on values of other fields like applications and replicas).

local defaults = {
  applications:: ['a'],
  replicas:: 10,
};
local apps = defaults + {
  local replicas = self.replicas,
  apps: {
    [x]: {
      replicas: replicas,
    }
    for x in self.applications # you need to use `self` because `default` refers to the specific fixed value
  }
};
apps {
  replicas:: 20,
  applications:: ['b'],
}

Which results in:

{
   "apps": {
      "b": {
         "replicas": 20
      }
   }
}

The difference is that the comprehension can be calculated after the whole inheritance business is done and in your example it needed to be ready before { replicas:: 20, applications:: ['b'] } was added.

You can also try a more function-based approach, which I like more in many cases, e.g.:

local defaults = {
  applications:: ['a'],
  replicas:: 10,
};

local apps(config) = {
  [x]: {
    replicas: config.replicas,
  }
  for x in config.applications 
};

apps(defaults { replicas:: 20, applications:: ['b'] })

Result:

{
   "b": {
      "replicas": 20
   }
}

Hope it helps!

By trying to use OO for this you're mixing up the input and the output. To keep them separate, you can do something like:

local defaults = {
  applications:: ['a'],
  replicas:: 10,
};

local apps = {
  output: {
    [x]: { 
      replicas: $.spec.replicas,
    } 
    for x in $.spec.applications
  },
  spec: defaults,
};

apps {
  spec+: {
    replicas:: 20,
    applications:: ['b'],
  } 
}   

But then you see that separation in the output too:

{
   "output": {
      "b": {
         "replicas": 20
      }
   },
   "spec": { }
}

(BTW, put the word jsonnet after the 3 ` chars in the markdown to get the syntax highlighting)

(And I'd just use a function in this case too)

Is there any plan to make the 3 features collaborate together at the final object level: generating multiple json files from an object that is derived from a template object which is a comprehensive object that takes parameters from the derived object?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sparkprime picture sparkprime  路  6Comments

mateuszpieniak picture mateuszpieniak  路  5Comments

ant31 picture ant31  路  6Comments

mbrukman picture mbrukman  路  5Comments

nnmrts picture nnmrts  路  9Comments