JSON++ Syntax Reference

JSON++ is a lightweight extension of JSON that adds three mechanisms to standard JSON:

  • structural composition for combining configuration documents (via inheritance with $extends and inclusion with $includes),

  • expression evaluation for computing values dynamically (via eval:), and

  • local node definitions for in-file reuse.

Structural composition and expression evaluation together form structural reuse — the full set of mechanisms for constructing configurations from reusable parts.

All JSON++ documents are valid JSON, so existing JSON tooling continues to work on JSON++ files. The jq++ tool evaluates a JSON++ file and emits ordinary JSON.

Semantic Summary

Construct Role Behavior Constraints / Notes

$extends: [f1, f2, …​]

Inheritance

Derives the current object from one or more parent definitions. Parents are resolved recursively, merged in listed order, and then overridden by the current object’s own fields.

Earlier parents take precedence over later ones. Priority is: current object → first parent → …​ → last parent. Circular resolution is rejected.

$includes: [f1, f2, …​]

Inclusion

Applies external fragments on top of the current object after $extends has been resolved. This is the opposite precedence direction from $extends.

Later included fragments take precedence over earlier ones. Priority is: last fragment → …​ → first fragment → current object. If both $extends and $includes are present, $includes wins over the combined result.

$local

Local node definitions

Defines reusable named objects inside the current file. Each local node is materialized into a temporary session-local file and can then be referenced by bare name from $extends or $includes.

$local is removed from the final output. Local nodes are visible within the composed subtree of the defining file.

"eval:<jq-expr>" and "eval:<type>:<jq-expr>"

Expression evaluation

A string value starting with eval: is compiled as a jq expression and evaluated against the fully composed JSON object. The result replaces the original string.

Value-side evaluation happens after structural composition and after key-side evaluation. Without a type annotation, the result must be a string. If the result is another eval: string, jq++ re-evaluates it up to a fixed iteration limit.

"eval:<jq-expr>" as a key

Computed keys

A key starting with eval: is evaluated and replaced with the resulting key name. If the expression yields an array of strings, one key is emitted per element.

Key-side evaluation runs before value-side evaluation. When evaluating a key, $cur points to the parent object.

raw:

Escaping

A key or string value starting with raw: is passed through unchanged except that the prefix is removed.

Guaranteed escape hatch for literal control-like strings and reserved keywords.[1]

Evaluation order

Resolution sequencing

jq++ first resolves file-level $extends / $includes, then materializes $local, then resolves nested $extends / $includes, then evaluates keys, and finally evaluates values.

This ordering makes precedence deterministic and ensures expressions see the fully composed structure.

JF_PATH

File resolution

Referenced files are resolved using jq++'s search path.

Resolution order is: current file directory → active local-node directories → JF_PATH entries. JF_PATH supplements, rather than replaces, relative resolution.

Output guarantee

JSON output

jq++ is a preprocessing step only. The result of evaluating JSON++ is always ordinary JSON.

Downstream tools consume the output exactly as JSON; JSON++ constructs do not survive evaluation unless explicitly escaped with raw:.

Special Keywords

JSON++ uses a small set of reserved keywords to express control semantics during evaluation. At present, the inheritance-time keywords are prefixed with $. These keywords are consumed during evaluation and do not appear in the output unless escaped.

$extends

Declares that the current object inherits from one or more parent objects. Parents are merged in the order listed, and the current object’s own fields override any inherited fields.

Syntax

{
  "$extends": ["parent1.json", "parent2.json"],
  "key": "override-value"
}

The value of $extends must be an array of filename strings. Each filename is resolved relative to the current file’s directory and the JF_PATH search path. Parent files may be in any supported format; the file extension determines how each file is parsed.

Merge order

Given $extends: [A, B], earlier entries in the array take precedence over later ones, and the current object’s own fields take precedence over all parents. In other words, the priority from highest to lowest is: current object → A → B.

Node-level inheritance

$extends can appear at any depth inside an object, not just at the file’s top level.

{
  "database": {
    "$extends": ["db-defaults.json"],
    "db_name": "production"
  }
}

Optional parent files

Append ? to a filename to make it optional. If the file does not exist, it is silently skipped.

{
  "$extends": ["base.json", "overrides.json?"]
}

$includes

Declares that the current object includes one or more external fragments to be applied on top of it. Unlike $extends, where the current object overrides its parents, $includes gives higher priority to the included fragments than to the including object’s own fields.

Syntax

{
  "$includes": ["fragment1.json", "fragment2.json"],
  "key": "value"
}

Fragment files may be in any supported format; the file extension determines how each file is parsed.

Merge order

Given $includes: [A, B], the merge order is the opposite of $extends: later entries in the array take precedence over earlier ones, and the included fragments take precedence over the current object’s own fields. In other words, the priority from highest to lowest is: B → A → current object.

Coexistence with $extends

$extends and $includes can appear in the same object. A useful way to remember their relationship is:

$includes overrides the current object, and the current object overrides $extends.

$extends is resolved first, then $includes is applied on top. The full priority order from highest to lowest is:

$includes (later entries) → $includes (earlier entries) → current object's own fields → $extends (earlier entries) → $extends (later entries)

Tip — building a custom DSL with $includes and JF_PATH

Because $includes fragments override the current object’s own fields, and because files are resolved via JF_PATH, you can use the two features together to build a lightweight domain-specific configuration language where platform-level fragments inject mandatory values.

Define a library of reusable fragments and place them in a directory on JF_PATH. Any configuration file can then pull in exactly the behaviour it needs with a single $includes line, without knowing the absolute location of the library:

{
  "$includes": ["defaults/runtime.json", "defaults/logging.json"],
  "log_level": "warn"
}

Running JF_PATH=/opt/myapp/lib jq++ service.json resolves defaults/runtime.json and defaults/logging.json from the library directory. Switching environments is a matter of changing JF_PATH—the configuration files themselves are untouched.

This pattern lets teams publish versioned "standard" fragments that any project can $includes without copying content. The result is a small but coherent DSL: every configuration file speaks the same vocabulary of shared fragments, and individual files only spell out what makes them different.

$local

Defines named local objects that can be referenced within the same file. Local nodes are materialized as temporary files during evaluation and removed from the output.

Syntax

{
  "$local": {
    "BaseThing": {
      "color": "blue",
      "size": 10
    }
  },
  "thing1": {
    "$extends": ["BaseThing"],
    "size": 20
  },
  "thing2": {
    "$extends": ["BaseThing"],
    "color": "red"
  }
}

Local names are referenced in $extends or $includes by their bare name (without a file extension). The $local key itself is removed from the output.

Expression Evaluation

String values (and string keys) that begin with eval: or raw: receive special treatment.

eval: prefix (values)

A string value starting with eval: is interpreted as a jq expression. The expression is evaluated against the current JSON root, and the result replaces the string.

Syntax

"key": "eval:<expression>"
"key": "eval:<type>:<expression>"

The optional <type> annotation constrains the expected result type. If the result does not match the declared type, evaluation fails with an error.

Supported type annotations

Annotation Expected result type

eval:string:<expr>

String

eval:number:<expr>

Number (integer or float)

eval:bool:<expr>

Boolean

eval:null:<expr>

Null

eval:object:<expr>

JSON object

eval:array:<expr>

JSON array

eval:<expr> (no type)

String (default)

Examples

{
  "version": "eval:string:\"v\" + (.major | tostring)",
  "count":   "eval:number:.items | length",
  "enabled": "eval:bool:.flags.debug",
  "tags":    "eval:array:[\"a\", \"b\"]"
}

If an eval: result is itself a string starting with eval:, it is re-evaluated (up to a fixed iteration limit). Circular references are detected and reported as errors.

eval: prefix (keys)

A key starting with eval: is evaluated and the result is used as the actual key name. If the expression evaluates to an array of strings, multiple keys are created—one per element—each with a copy of the value.

Syntax

{
  "eval:<expression>": "<value>"
}

The expression must evaluate to a string or an array of strings.

Example

{
  "languages": ["en", "ja", "fr"],
  "eval:.languages": {"supported": true}
}

Output:

{
  "languages": ["en", "ja", "fr"],
  "en": {"supported": true},
  "ja": {"supported": true},
  "fr": {"supported": true}
}

When evaluating a key expression, the builtin variable $cur holds the path to the parent object (not to the key itself).

raw: prefix

A string value or key starting with raw: is passed through with the prefix stripped, without any evaluation. This is an escape mechanism for strings that would otherwise be mistaken for eval: expressions.

Syntax (value)

{
  "key": "raw:eval:this is not an expression"
}

Output:

{
  "key": "eval:this is not an expression"
}

Syntax (key)

{
  "raw:eval:literal-key": "value"
}

Output:

{
  "eval:literal-key": "value"
}

Tip — escaping reserved keywords

raw: is the formal, guaranteed way to produce a literal key (or value) that would otherwise match a JSON++ control prefix such as eval: or a reserved keyword such as $extends, $includes, or $local. Because these strings are interpreted by jq++ during evaluation, prefixing with raw: is the portability-safe way to ensure they survive unchanged in the final JSON.

In practice, jq++ keeps inheritance-time control keywords in the $…​ namespace, and introducing new reserved prefixes or new keyword families is treated as a major-version compatibility change. That policy makes accidental collisions unlikely, but raw: remains the documented escape hatch.

{
  "raw:$extends": "this key appears verbatim in the output",
  "raw:$local":   "so does this one"
}

Output:

{
  "$extends": "this key appears verbatim in the output",
  "$local":   "so does this one"
}

Builtin Variables

The following variables are injected automatically into every eval: expression context.

$cur

The path to the current node, represented as a path array. Each element is a string (object key) or integer (array index). + When evaluating a value, $cur is the path to that value. When evaluating a key, $cur is the path to the parent object. + Example: for a value at .foo.bar[0], $cur is ["foo", "bar", 0].

$curexpr

The path to the current node as a path expression string (e.g. ".foo.bar[0]"). Available only when evaluating a value; not available when evaluating a key.

Builtin Functions

The following functions are available inside eval: expressions.

Path Conversion

Note

A note on path expressions in jq

In standard jq, a path expression (e.g. .foo.bar[0]) is a syntactic construct, not a value. It exists only at the language level—as a fragment of source code—and cannot be stored in a variable, passed as an argument, or returned from a function. Functions such as path/1, getpath/1, and setpath/1 accept path arrays (plain JSON arrays of strings and integers) precisely because path expressions are not first-class data.

JSON++ intentionally departs from this principle. $cur and $curexpr expose the current location as runtime values, and topatharray/topathexpr let you convert between the two representations freely. This is a deliberate usability trade-off: configuration authors need to navigate and reference locations in the document, and string-based path expressions are considerably more readable than raw path arrays when written inside a JSON string.

Treating path expressions as strings is therefore a JSON++-specific convention. It has no meaning to jq itself; the conversion functions bridge the gap when you need to pass a location to a jq built-in such as getpath or setpath.

topatharray(path_expression_string)

Converts a path expression string such as ".foo.bar[0]" into a path array ["foo", "bar", 0].

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

Output: {"result": ["foo", "bar", 0]}

topathexpr(path_array)

Converts a path array into a path expression string.

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

Output: {"nested": {"value": ".nested"}}

Parent Navigation

parent / parent(level)

Returns the path array of the parent of the current node. Without arguments, goes up one level. With one integer argument n, goes up n levels. Uses the implicit $cur variable.

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

Output: {"a": {"b": {"c": ".a.b"}}}

parentof(path_array) / parentof(path_array; level)

Removes the last segment(s) from the given path array. The first argument is any path array; the optional second argument is how many segments to remove (default 1).

{
  "version": "1.0",
  "meta": {
    "deep": {
      "v": "eval:string:ref(parentof($cur; 3) + [\"version\"])"
    }
  }
}

Output: {"version": "1.0", "meta": {"deep": {"v": "1.0"}}}

Reference and Lookup

ref(path_array)

Returns the value in the current JSON root at the given path array. If the value is a string, it is evaluated as an expression (allowing chained references). Circular references are detected and cause an error.

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

Output: {"shared": "common value", "node": {"copy": "common value"}}

refexpr(path_expression_string)

Like ref, but takes a path expression string rather than a path array.

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

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

reftag(tag_name)

Searches upward from the current path for an ancestor 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. Useful for implementing "tagged" reference points in deeply nested trees.

{
  "label": "root-label",
  "section": {
    "item": {
      "inherited": "eval:reftag(\"label\")"
    }
  }
}

Output: {"label": "root-label", "section": {"item": {"inherited": "root-label"}}}

File Operations

readfile(filename_string)

Resolves filename_string relative to the current file’s directory and JF_PATH, reads the file, and parses it as JSON. The result is the parsed JSON value (object, array, string, number, boolean, or null).

{
  "config": "eval:object:readfile(\"defaults.json\")"
}

Supported Input Formats

jq++ accepts files in any of the following formats. The format is determined from the file extension.

Extension Format

.json, .json++

JSON

.yaml, .yml, .yaml`, `.yml

YAML

.toml, .toml++

TOML

.json5, .json5++

JSON5

.hocon, .conf, .hocon`, `.conf

HOCON

.jq

jq module (custom functions available in eval expressions)

YAML, TOML, JSON5, and HOCON files are converted to the JSON data model before processing; the same JSON++ directives work in all formats. The ` suffix variants (`.json, .yaml++, etc.) are treated identically to their base extensions.

Search Paths

When resolving filenames in $extends, $includes, and readfile(), jq++ searches directories in the following order:

  1. The directory of the file that contains the reference.

  2. Directories listed in the JF_PATH environment variable, separated by : (colon).

This mirrors the behavior of environment variables such as PATH or PYTHONPATH.

$ JF_PATH=/shared/configs:/team/defaults jq{plus}{plus} myconfig.json

See the Design page for a detailed description of the evaluation pipeline.


1. raw: is the formal, guaranteed way to preserve values or keys that would otherwise be treated as JSON++ control syntax. In practice, jq++ keeps inheritance-time control keywords in the $…​ namespace, and introducing new reserved prefixes or new keyword families is treated as a major-version compatibility change.