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
$extendsand 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.
Special Keys
JSON++ uses a small set of reserved keys—all prefixed with $—to express structural relationships.
These keys are consumed during evaluation and do not appear in the output.
$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 as authoritative defaults.
Unlike $extends, where the derived object overrides its parents, $includes inverts the relationship: the current object’s own fields serve as defaults that the included fragments can override.
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 |
|---|---|
|
String |
|
Number (integer or float) |
|
Boolean |
|
Null |
|
JSON object |
|
JSON array |
|
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 keys
raw: is the only way to produce a literal key (or value) that starts with a JSON reserved prefix such as `$extends`, `$includes`, or `$local`.
Because these strings are consumed by jq before output, a bare "$extends" key cannot survive into the final JSON.
Prefix it with raw: to pass it through unchanged.
{
"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,
$curis the path to that value. When evaluating a key,$curis the path to the parent object. + Example: for a value at.foo.bar[0],$curis["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. JSON++ intentionally departs from this principle.
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 |
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 |
|
YAML |
|
TOML |
|
JSON5 |
|
HOCON |
|
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:
-
The directory of the file that contains the reference.
-
Directories listed in the
JF_PATHenvironment variable, separated by:(colon).
This mirrors the behavior of environment variables such as PATH or PYTHONPATH.
$ JF_PATH=/shared/configs:/team/defaults jq++ myconfig.json
See the Design page for a detailed description of the evaluation pipeline.