Navigation Expressions Reference¶
Introduction¶
JFTL navigation implement a subset of the JSONPath grammar, extended with variables inspired by the SQL/JSON standard.
Every navigation expression in JFTL resolve to a single JSON Value (from the original document, from the template, from external data, or calculated) — like a street address, customer object. If you need to work over many items — filtering, selecting, looping — that's what foreach, check, and case are for (see Logic).
Tactically, JFTL navigation expressions are singular queries, in the sense defined by the JSONPath standard (RFC 9535 §2.3.5.1): the grammar is restricted so that every valid expression is guaranteed, by construction, to resolve to at most one value — never a list of matches. There is no filtering, recursive descent, wildcards, slices, or unions; navigation is not a general query language, and isn't intended as one.
If any path component cannot be resolved, the result is Missing.
JFTL navigation does not support filters, recursive descent, wildcards, slices, unions, or any other JSONPath constructs that may select multiple nodes. Conditional logic and iteration are instead provided by JFTL's check, case, and foreach constructs (see Logic).
Every navigation expression resolves to exactly one value:
- the referenced JSON value, if the path exists;
Missing, if any component of the path cannot be resolved.
Navigation expressions never return lists of matching nodes. More complex selection and filtering are performed using JFTL's foreach, check, and case constructs (see Logic).
Navigation Roots¶
Every navigation expression begins with one of the following roots.
| Root | Description |
|---|---|
$ |
Current data. |
$foo |
User-defined variable named foo. |
$% |
Variable namespace. |
$< |
Current data of the enclosing logic element. |
$^ |
The input document. |
The following sections describe each navigation root in detail.
$ — Current Data¶
$ refers to the current data.
Examples:
$
$.customer
$.items[42]
Within a logic element, the current data may be replaced by the data statement or by each iteration of foreach.
$foo — Named Variable¶
$foo references a user-defined variable named foo.
Variables are introduced by set or by foreach.
Examples:
$user
$order.customer
$item.price
$foo is equivalent to $%.foo.
Variable scoping rules are described in Variables.
3## $% — Variable Namespace
$% exposes the current variable scope as an object whose members are variables.
Like any other object, variables may be accessed using identifier notation, quoted keys, or computed keys.
Examples:
$%.customer
$%["customer id"]
$%[$name]
This makes it possible to reference variables whose names are determined at runtime or whose names are not valid identifiers.
The variable namespace participates in normal navigation, so additional path segments may follow the selected variable.
Examples:
$%.customer.address
$%[$name].zip
Variable scoping rules are described in Variables.
$< — Caller Frame¶
$< refers to the Caller Frame when used inside a function.
Unlike JSON navigation, the enclosing logic element is determined by the evaluation structure rather than by the structure of the JSON document. Consequen
- The same JSON value as the current logic element;
- a parent of the current value;
- a descendant of the current value; or
- a completely unrelated JSON value.
The relationship depends entirely on how each logic element selected its current data (for example, using data or foreach.in).
A common case is nested foreach loops. When each foreach iterates over a child collection of its current data, and neither logic element explicitly replaces its current data using data, the enclosing logic element's current data is the direct parent of the current value.
Examples:
$<
$<.customer
Use $< whenever a nested logic element needs to access the current data of its enclosing logic element.
$< across component calls (function / macro)¶
Calling a component ({"$": "componentName", ...}, see defs.md) also creates a new evaluation frame, so $< behaves differently depending on the component's type — and it behaves differently from what you might expect, because it does not simply mean "the caller":
-
macro— the macro body's frame is a child of the calling frame.$<inside a macro is the caller's current data at the call site — the same value$held in the caller, immediately before the call. This is what most people expect$<to mean. -
function— the function body's frame is a child of the top-level frame, not the calling frame, regardless of how deeply nested the call is.$<inside a function is therefore the input document's current data — the same value$^would give — and it skips over the immediate caller entirely.
Both function and macro still receive the caller's current data as their own $ (a function's frame explicitly copies it in), so $ behaves the same either way. Only $< differs.
{
"defs": {
"tag_macro": { "type": "macro", "out": { "seen_via_lt": "$<" } },
"tag_function": { "type": "function", "out": { "seen_via_lt": "$<" } }
},
"main": {
"$": true,
"data": "$.product",
"out": {
"from_macro": { "$": "tag_macro" },
"from_function": { "$": "tag_function" }
}
}
}
Input:
{
"product": { "id": "p1" },
"other": "ignored"
}
Result:
{
"from_macro": {
"seen_via_lt": { "id": "p1" }
},
"from_function": {
"seen_via_lt": { "product": { "id": "p1" }, "other": "ignored" }
}
}
main narrows its current data to $.product before making either call. The macro's $< sees that narrowed value ({"id": "p1"}), because its frame's parent is main's own frame. The function's $< sees the full, un-narrowed input document instead, because its frame's parent is the top-level frame — data: $.product never touched it.
Component parameters are not reached through
$<. Values passed as call parameters ({"$": "greet", "name": "$.customer"}) become ordinary variables inside the component, looked up with$name, exactly like anysetvariable — never with$<.$<only ever exposes current data, never variables.
$^ — The input document (The "Top" of the document)¶
$^ refers to the full input document (technically, it's current data of the outermost logic element). $^ provide access to the complete input document, regardless of the current nesting level or any intermediate data statements.
Examples:
$^
$^.customer
Unlike $<, which refers to the immediately enclosing logic element, $^ always refers to the top-level current data (the original input document).
Path Segment Types¶
After the root, a navigation expression consists of zero or more path segments.
Segments are evaluated from left to right. If any segment cannot be resolved, evaluation immediately returns Missing.
The following segment types are supported:
| Segment | Description |
|---|---|
.name |
Access an object member whose name is a valid identifier. |
[42] |
Access an array element by its zero-based index. |
[-3] |
Access an array element counting from the end of the array (-1 is the last element). |
['key'] |
Access an object member using a single-quoted key. |
["key"] |
Same as above, using double quotes. Single and double quotes are interchangeable. |
[$var] |
Access an object member using a computed key |
The following sections describe each segment type in detail.
Object Member (.name)¶
Object members are accessed using dot notation.
$.customer
$.customer.address
$order.total
As in JSONPath, dot notation may be used only for member names that are valid identifiers. All other member names must use quoted notation.
Examples:
$.customer["first name"]
$.customer["postal-code"]
$.customer["123"]
If the member does not exist, the result is Missing.
Array Index ([42], [-3])¶
Array elements are accessed using square brackets.
$.items[42]
$.orders[1].lines[3]
$.matrix[2][5]
Indexes are zero-based.
Negative indexes count from the end of the array.
$.items[-1] # last element
$.items[-2] # second-to-last element
$.items[-3] # third-to-last element
If the index is outside the array bounds, the result is Missing.
Quoted Object Keys (['key'] / ["key"])¶
Object keys that are not valid identifiers may be written inside square brackets.
Both single and double quotes are accepted and are completely interchangeable.
The following expressions are equivalent:
$.customer["first name"]
$.customer['first name']
Unlike JSON strings, quoted member names are simple string literals and do not support escape sequences. If a member name contains a quote character, use the other quoting style.
Examples:
$.cities["New York"]
$.config['default-value']
If the key does not exist, the result is Missing.
Computed Keys ([$var])¶
The key inside brackets may be a user-defined variables.
$.countries[$country]
$.settings[$key]
The expression must evaluate to a string if navigating to an object member. The expression must evaluate to an integer if navigating into an array. In this case, the regular negative indexing will apply.
If the value being navigated is Missing, or if the key expression evaluates to Missing, or the resulting key does not exist, the overall navigation expression also evaluates to Missing.
Missing Values¶
Navigation expressions never fail simply because a path cannot be resolved.
Instead, a missing object member, array element, variable, or computed key evaluates to Missing.
This allows navigation expressions to compose naturally with JFTL's check, fallback, case, and other language constructs.
Built-in Variables¶
JFTL provides several built-in variables in addition to user-defined variables.
These include local and global variable scopes (_local, _global), the current data (_), and variables introduced by foreach.
See Variables for the complete list of built-in variables and their scoping rules.
Examples¶
| Expression | Description |
|---|---|
$.customer.address.city |
Retrieve the city field from the current customer's address. |
$.orders[42].total |
Retrieve the total field from the 43rd order. |
$.orders[-1].total |
Retrieve the total field from the last order. |
$.cities["New York"] |
Access an object key containing spaces using double quotes. |
$.cities['New York'] |
Same as above, using single quotes. |
$.countries[$country] |
Use the value of the variable country as the object key. |
$user.address.zip |
Access the zip field of the user-defined variable user. |
$%[$name] |
Access the variable whose name is stored in the variable name. |
$<.customer |
Access the customer field from the current data of the enclosing logic element. |
$^.config |
Access the config field from the original input document. |
See Also¶
- Logic — Logic elements and the execution pipeline.
defs.md— Component (function/macro) definitions and calls, including how$<behaves across a call boundary.- Variables — Built-in variables, user-defined variables, and scoping rules.
- Expression-Engines —
$py=,$pyeval=,$pyrun=, and other expression engines. - Interpolation —
${...}string interpolation.