Builtin variables

jq++ injects the following variables into every eval: expression. They are available as jq variables (e.g. $cur, $curexpr).

$cur

The current path as a path array (JSON array of path segments: object keys as strings, array indices as integers). When evaluating a value (eval: in a value), $cur is the path to that value. When evaluating a key (eval: in a key), $cur is the path to the parent object (the path of the object that contains the key being computed). Use with parent, parentof, ref, or topathexpr to navigate or refer to locations.

$curexpr

The current path as a path expression string (e.g. ".foo.bar[0]"). Available only when evaluating a value; not set when evaluating a key. Useful for debugging or when you need the path in string form.

Builtin Functions

jq++ provides builtin functions that are available inside eval: expressions. Each function is described below with example usage. Examples use the eval:string: or eval:array: prefix so the expression result is used as the value of the key.

Path conversion

topatharray

Signature: topatharray(path_expression_string)

Converts a path expression string (e.g. ".foo.bar[0]") into a path array: a JSON array of path segments (object keys as strings, array indices as integers). Useful for manipulating paths programmatically and passing them to ref or parentof.

Example: examples/builtins/topatharray/input.json

{
  "pathArray": "eval:array:topatharray(\".foo.bar[0]\")"
}

Result: The key pathArray becomes the array corresponding to the path expression:

{
  "pathArray": ["foo", "bar", 0]
}

topathexpr

Signature: topathexpr(path_array)

Converts a path array (slice of keys and indices) into a path expression string (e.g. ".foo.bar[0]"). Non-alphanumeric keys are quoted in bracket notation.

Example: examples/builtins/topathexpr/input.json

{
  "nested": {
    "pathExpr": "eval:string:topathexpr(parent)"
  }
}

Result: The key pathExpr becomes the path expression string for the current parent path:

{
  "nested": {
    "pathExpr": ".nested"
  }
}

Parent path

parent

Signature: parent or parent(level)

Returns the path array of the parent of the current node. With no arguments, goes up one level; with one integer argument, goes up that many levels. Used with the builtin variable $cur (current path array). Typically used as parent or parent(2) inside expressions.

Example: examples/builtins/parent/input.json

{
  "a": {
    "b": {
      "value": "eval:string:topathexpr(parent)"
    }
  }
}

Result: At a.b.value, topathexpr(parent) yields the path to the parent key b:

{
  "a": {
    "b": {
      "value": ".a.b"
    }
  }
}

parentof

Signature: parentof(path_array) or parentof(path_array; level)

Returns the path array with the last segment(s) removed. First argument is a path array; optional second argument is the number of levels to remove (default 1). Often used with $cur to go up from the current path, e.g. parentof($cur; 2) then append a key and pass to ref.

Example: examples/builtins/parentof/input.json

{
  "releaseVersion": "2.0.0",
  "l1": {
    "l2": {
      "snapshotVersion": "eval:string:ref(parentof($cur; 3) + [\"releaseVersion\"]) + \"-SNAPSHOT\""
    }
  }
}

Result: releaseVersion at root is copied into l1.l2.snapshotVersion using parentof($cur; 3) to reach root:

{
  "releaseVersion": "2.0.0",
  "l1": {
    "l2": {
      "snapshotVersion": "2.0.0-SNAPSHOT"
    }
  }
}

Reference and lookup

ref

Signature: ref(path_array)

Returns the value in the current JSON root at the given path array. If that value is a string, it is evaluated as an expression (e.g. another reference or expression). Path array is built from strings (object keys) and integers (array indices). Circular references are detected and reported as errors.

Example: examples/builtins/ref/input.json

{
  "shared": "common value",
  "node": {
    "usesShared": "eval:string:ref([\"shared\"])"
  }
}

Result: The key that uses ref(["shared"]) is replaced by the value at root shared:

{
  "shared": "common value",
  "node": {
    "usesShared": "common value"
  }
}

refexpr

Signature: refexpr(path_expression_string)

Like ref, but takes a path expression string (e.g. ".foo.bar") instead of a path array. Resolves the path from the root and returns the value; if it is a string, that string is evaluated as an expression.

Example: examples/builtins/refexpr/input.json

{
  "source": "original",
  "copy": "eval:string:refexpr(\".source\")",
  "chained": "eval:string:refexpr(\".copy\")"
}

Result: copy and chained resolve to the value of source:

{
  "source": "original",
  "copy": "original",
  "chained": "original"
}

reftag

Signature: reftag(tag_name)

Searches upward from the current path for an object that has a key equal to tag_name, then returns the value at that key. If that value is a string, it is evaluated as an expression. Used to implement “tagged” reference points in the tree.

Example: examples/builtins/reftag/input.json

{
  "thetag": "Hello, mytag",
  "k0": {
    "k1": "eval:string:reftag(\"thetag\")"
  }
}

Result: The key using reftag("thetag") is replaced by the value of thetag from an ancestor object:

{
  "thetag": "Hello, mytag",
  "k0": {
    "k1": "Hello, mytag"
  }
}

File

readfile

Signature: readfile(filename_string)

Resolves the filename relative to the current file’s directory and search paths, reads the file, and parses its contents as JSON. The result is the parsed JSON value (object, array, string, number, boolean, or null). Often used with eval:string: when the file contains a single string to use as a string value.

Example: examples/builtins/readfile/input.json (and other.json in the same directory).

{
  "key": "eval:string:readfile(\"other.json\")"
}

Result: The key using readfile("other.json") is replaced by the parsed content of other.json (here a string):

{
  "key": "hello"
}

open this page in a window

jq++: JSON with inheritance and templating

jq++ is a tool that extends your JSON with file- and node-level inheritance, templating, and reference evaluation. It productizes the ideas of jq-front, which was named after Cfront[3]—the classic C++-to-C translator—and gives JSON a similar “front-end” power.

Despite criticism of JSON[6] as a format for configuration[4], JSON has clear strengths: broad tool support, a good balance between machine and human readability, and simple, unambiguous syntax.

Where JSON falls short is in structure. When you need many similar but slightly different configurations, redundancy is hard to remove because JSON does not define relationships between files or between nodes inside a file.[1]

What is it good for?

Have you ever had to maintain configuration files that are almost the same, but not quite?

config for foo config for bar
{
  "foo_database": {
    "server": {
      "ip": "192.168.1.5",
      "port": 2001
    },
    "db_name": "foo",
    "user": {
      "name": "root",
      "password": "foo_root"
    }
  }
}
{
  "bar_database": {
    "server": {
      "ip": "192.168.1.5",
      "port": 2001
    },
    "db_name": "bar",
    "user": {
      "name": "root",
      "password": "bar_root"
    }
  }
}

With jq++, you can define a single base JSON for the common parts:

database_default
{
    "server": {
      "ip": "192.168.1.5",
      "port": 2001
    },
    "user": {
      "name": "root"
    }
}

Then reuse it from other files via $extends:

foo.json bar.json
{
  "foo_database": {
    "$extends": [ "database_default.json" ],
    "db_name": "foo",
    "user": {
      "password": "foo_root"
    }
  }
}
{
  "bar_database": {
    "$extends": [ "database_default.json" ],
    "db_name": "bar",
    "user": {
      "password": "bar_root"
    }
  }
}

Run jq foo.json` and you get the expanded “config for foo” from the first table; `jq bar.json gives you the “config for bar”. You keep one source of truth and generate each variant by extending it.

Templating and references

jq++ also supports templating and references so you can embed values and run shell commands in string fields.

Example: extend two files and build a greeting string from their fields and the current date:

sayHello.json
{
  "$extends": ["greeting.json", "name.json"],
  "sayHello": "eval:refexpr(\".greeting\") + \", \" + refexpr(\".yourname\") + \". Today is \" + (now|todate)"
}

With greeting.json as {"greeting":"Hello"} and name.json as {"yourname":"Mark"}, running jq++ sayHello.json produces:

{
  "greeting": "Hello",
  "sayHello": "Hello, Mark. Today is 2026-02-14T13:07:57Z",
  "yourname": "Mark"
}

YAML?

JSON is often criticized as a configuration language[4]. Whether or not you agree, jq can still improve a YAML-based workflow: you keep YAML for editing, then convert to JSON, run jq for inheritance and templating, and optionally convert back to YAML.

INPUT OUTPUT
$local:
  database_default:
    server:
      ip: 192.168.1.5
      port: 2000
    db_name: test
    user:
      name: root
      password: root

foo_database:
  $extends: [ database_default ]
  server:
    port: 2001
  db_name: foo
  user:
    password: foo_root
foo_database:
  server:
    ip: 192.168.1.5
    port: 2001
  db_name: foo
  user:
    name: root
    password: foo_root

Example pipeline (using yq for YAML↔JSON):[8]

$ yq . -j in.yaml | jq++ | yq . -y

HOCON?

HOCON[hocon] is another human-friendly format with inheritance and references. You might still prefer jq++:

  • Standard JSON in and out. jq++ reads and writes JSON, which fits into almost any toolchain.

  • Custom behavior via templating. You can define how values are computed (references, eval:, etc.) without leaving JSON.

  • Separation of concerns. Data structure (inheritance, references) and human-friendly syntax are different problems; jq++ focuses on structure so you can choose the syntax (JSON, YAML, etc.) separately.

That separation matters in automation and CI/CD. Configs often need to be generated by programs; JSON is easy to generate and parse. HOCON, as a superset of JSON, is less straightforward to emit: a program might produce valid HOCON that looks different from hand-written style after a round trip. If you use jq++ for the “structure and value resolution” step and keep JSON (or YAML) as the interchange format, you avoid mixing style and semantics in one layer.

You can combine jq++ with any JSON-oriented tools—including jq[7] for querying and transformation—and still improve readability by editing in YAML or another format upstream.

open this page in a window