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.
Semantic Summary
| Construct | Role | Behavior | Constraints / Notes |
|---|---|---|---|
|
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. |
|
Inclusion |
Applies external fragments on top of the current object after |
Later included fragments take precedence over earlier ones.
Priority is: last fragment → … → first fragment → current object.
If both |
|
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 |
|
|
Expression evaluation |
A string value starting with |
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 |
|
Computed keys |
A key starting with |
Key-side evaluation runs before value-side evaluation.
When evaluating a key, |
|
Escaping |
A key or string value starting with |
Guaranteed escape hatch for literal control-like strings and reserved keywords.[1] |
Evaluation order |
Resolution sequencing |
jq++ first resolves file-level |
This ordering makes precedence deterministic and ensures expressions see the fully composed structure. |
|
File resolution |
Referenced files are resolved using jq++'s search path. |
Resolution order is: current file directory → active local-node directories → |
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 |
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 |
|---|---|
|
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 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,
$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{plus}{plus} myconfig.json
See the Design page for a detailed description of the evaluation pipeline.