Skip to content

Jsonnet

References, variables, simple JSON templating

You can think of Jsonnet as a domain-specific language
that can be extended to provide templating for other
languages. Think JSON, but with:

This introdution focuses on the first three items.

Local variables and references

In Jsonnet, you can define lexically-scoped local variables:

{
  local foo = "bar",
  baz: foo,
}

which produces:

{ "baz": "bar" }

Jsonnet also exposes a self to access properties of the
current object, and a JsonPath-style $, which refers to the root
object (the grandparent that is farthest away from the $):

{
  foo: "bar",
  baz: self.foo,
  cow: {
    moo: $.foo,
  },
}
{
  "foo": "bar",
  "baz": "bar",
  "cow": { "moo": "bar" }
}

It is worth noting that both local variables and references are
order-independent, which is a decision that largely falls out of
JSON's design. Notice, for example, that if we re-order foo and
baz, it does not affect the output of Jsonnet:

{
  baz: self.foo,
  cow: {
    moo: $.foo,
  },

  // This is perfectly legal.
  foo: "bar",
}

Functions

Jsonnet implements lexically-scoped functions, but they can be
declared in a few ways, and it's worth pointing them out.

In the example below, note the use of the double colon (::) in
the declaration of function2. This marks the field as hidden,
which is a concept we will look closer at in the section on
object-orientation. For now, it is only important to understand that a
function must be either local or hidden with ::, because Jsonnet
doesn't know how to render a function as JSON data. (Instead of
rendering it, Jsonnet will complain and crash.)

{
  local function1(arg1) = { foo: arg1 },
  function2(arg1="cluck"):: { bar: arg1 },
  cow: function1("moo"),
  chicken: self.function2(),
}
{
   "chicken": {
      "bar": "cluck"
   },
   "cow": {
      "foo": "moo"
   }
}

Object-orientation (inheritance, mixins)

One of Jsonnet's most powerful features, which we use liberally in
this tutorial and in ksonnet, is its object model, which
implements a concise, well-specified algebra for
combining JSON-like objects.

The primary tool for combining objects is the + operator. In this
example we see two objects (the first is called the parent, or
base, and the second is called the child) that are combined with
the +. The child (which is said to inherit from the parent)
overwrites the bar property that was defined in the parent:

{
  // Parent object.
  foo: "foo",
  bar: "bar",
} + {
  // Child object.
  bar: "fubar",
}
{
   "bar": "fubar",
   "foo": "foo"
}

It is sometimes convenient for a child to reference members of the
parent, so Jsonnet also exposes super, which behaves a lot like
self, except in reference to the parent:

{
  foo: "foo",
} + {
  bar: super.foo + "bar",
}
{
   "bar": "foobar",
   "foo": "foo"
}

One interesting aspect of super is that it can be "mixed in",
meaning that if you have an object that refers to super.bar, then it
can dynamically be made to inherit from any object that has a bar
property. For example:

local fooTheBar = { bar: super.bar + "foo" };
{
  bar: "bar",
} + fooTheBar
{
   "bar": "barfoo"
}

This stands in contrast to the object model of (say) Java, where you
would have to declare at compile time an Animal class before a Dog
class could be made to inherit from it. The technique above (called a
mixin) causes the object to inherit dynamically, at runtime rather
than compile time.

Lastly, Jsonnet allows you to create hidden properties, not included
when we generate the final JSON. Denoted with with a ::, they are
also visible to all descendent objects (i.e., children,
grandchildren, etc.), and are useful for holding data you'd like to
use to construct other properties, but not expose as part of the
generated JSON itself:

{
  foo:: "foo",
} + {
  bar: super.foo + "bar",
}
{
   "bar": "foobar"
}

Evaulating

import json
import _jsonnet

jsonnet_str = '''
{
  person1: {
    name: "Alice",
    welcome: "Hello " + self.name + "!",
  },
  person2: self.person1 {
    name: std.extVar("OTHER_NAME"),
  },
}
'''

json_str = _jsonnet.evaluate_snippet(
    "snippet", jsonnet_str,
    ext_vars={'OTHER_NAME': 'Bob'})

json_obj = json.loads(json_str)
for person_id, person in json_obj.iteritems():
  print '%s is %s, greeted by "%s"' % (
      person_id,
      person['name'],
      person['welcome'])

Recursive evaluation (compare to json)

# Provide a superset of required parameters
for i in `find Project1/manifests Project2/manifests   -name "*.jsonnet"`;
do
  jsonnet -V param1=1 -V param2=dummy -V param3=1 "${i}" >> /dev/null
done;
# This will lint-test the files, including libsonnet files.
for i in `find Project1 Project2 -name "*.*sonnet"`;
do
  jsonnet fmt -i -n 2 "${i}" --test
done;

Conditionally adding item to list

  ports: [
           {
             port: $.httpPort,
             targetport: $.httpTargetPort,
           },
           {
             port: $.jnlpPort,
             targetport: 15372,
           },
         ] +
         (if $.sshEnabled then
            [{
              port: $.sshPort,
              targetport: 15373,
            }] else []),

Conditionally addint attributes to object/map

defaultContainerEnv:: {
    LOGGING_STDERR_LEVEL: "ALL",
    JENKINS_USER: "jenkins",
  } + (if $.sshEnabled then {
          SSH_PORT: 7012,
        } else {}
      )