Programming
JSONata is a functional language. Values are immutable, functions are values, and everything is an expression.
Blocks
Parentheses group expressions separated by ;. The expressions are evaluated in order, and
the block’s value is the value of the last one. A block is also a scope for variables.
(1 + 2; "ignored"; 10 * 2)
=> 20
Variables
A name that starts with $ is a variable. $name := value binds a variable, and its value is
the bound value. The binding is visible in the rest of the block and in nested blocks, but not
outside the block.
input: invoice.json
Account.Order.Product.(
$total := Price * Quantity;
$tax := $total * 0.2;
$round($total + $tax, 2)
)
=> [82.68, 26, 165.36, 129.59]
($x := 1; ($x := 2); $x)
=> 1
Predefined variables:
$is the context value; see Paths.$$is the root of the input.- The built-in functions (
$sum,$string, …) are variables too, so a binding can replace one in its scope.
Applications can define more variables when they evaluate an expression: bindings, such as
jlens eval --bindings, Bindings in Rust, or the bindings argument in JavaScript.
bindings: {"limit": 100, "rate": 0.2}
$limit * (1 + $rate)
=> 120
Functions
Calling functions
A function is called with its arguments in parentheses:
$substring("hello world", 0, 5)
=> "hello"
Many built-in functions use the context value when their first argument is omitted (their
signature marks that argument with -). That makes them usable as path steps:
input: invoice.json
Account.Order.OrderID.$uppercase()
=> ["ORDER103", "ORDER104"]
Defining functions
function($a, $b) { body } defines a function. λ can be written instead of function.
Functions are values: bind them to variables, pass them to other functions, return them.
(
$volume := function($l, $w, $h) { $l * $w * $h };
$volume(10, 10, 5)
)
=> 500
λ($x) { $x * 2 }(21)
=> 42
Missing arguments are undefined inside the function, and extra arguments are ignored.
Signatures
An optional signature after the parameter list declares the types of the arguments and of the
result. Arguments are then checked when the function is called (errors T0410 and T0412).
(
$repeat := function($s, $n)<sn:s> { $join([1..$n].($s)) };
$repeat("ab", 3)
)
=> "ababab"
(
$repeat := function($s, $n)<sn:s> { $join([1..$n].($s)) };
$repeat(3, "ab")
)
=> error T0410
A signature is <params:result>, with one symbol per parameter:
| Symbol | Type |
|---|---|
b | Boolean |
n | number |
s | string |
l | null |
a | array; a<n> is an array of numbers |
o | object |
f | function |
(sn) | a choice: string or number |
u | (bnsl): any simple value |
j | (bnsloa): any JSON value |
x | any value, functions included |
These modifiers can follow a symbol:
?makes the parameter optional.+takes one or more arguments of the type.-takes the context value when the argument is missing.
A single value is accepted where an array is expected. For example, $sum has the signature
<a<n>:n> and $length has <s-:n>. The function reference gives the
signature of every built-in function.
Recursion and tail calls
A function bound to a variable can call itself through it:
(
$factorial := function($n) { $n <= 1 ? 1 : $n * $factorial($n - 1) };
$factorial(10)
)
=> 3628800
Each nested call uses stack, and evaluation stops with D1011 past a depth limit (see
Guardrails). A call in tail position, the last
thing a function does, does not nest. jlens runs it as a loop, so tail-recursive functions can
iterate any number of times:
(
$sumTo := function($n, $acc) { $n = 0 ? $acc : $sumTo($n - 1, $acc + $n) };
$sumTo(100000, 0)
)
=> 5000050000
Closures
A function remembers the variables and the context value of the place where it was defined:
(
$multiplier := function($k) { function($x) { $x * $k } };
$triple := $multiplier(3);
[$triple(2), $triple(5)]
)
=> [6, 15]
input: invoice.json
Account.(
$account := function() { $.`Account Name` };
Order[OrderID = 'order104'].Product.{"account": $account(), "product": `Product Name`}
)
=> [
{"account": "Firefly", "product": "Bowler Hat"},
{"account": "Firefly", "product": "Cloak"}
]
Higher-order functions
Functions that take or return functions are called higher-order. The library has several:
$map, $filter,
$reduce, $sift,
$single and $sort.
(
$twice := function($f) { function($x) { $f($f($x)) } };
$twice(function($y) { $y + 3 })(7)
)
=> 13
Partial application
Calling a function with ? in place of some arguments returns a new function that takes the
missing ones:
(
$first5 := $substring(?, 0, 5);
$first5("Hello, World")
)
=> "Hello"
(
$firstN := $substring(?, 0, ?);
$firstN("Hello, World", 3)
)
=> "Hel"
Function composition
When both sides of ~> are functions, the result is their composition: a function that applies
the left one, then the right one.
(
$normalize := $trim ~> $uppercase;
$normalize(" Some Words ")
)
=> "SOME WORDS"
input: address.json
(
$first5Capitalized := $substring(?, 0, 5) ~> $uppercase(?);
$first5Capitalized(Address.City)
)
=> "WINCH"
Conditional logic
The conditional operator ? : and the
default operators ?: and ?? cover if/then/else. Nested
conditionals read like an else if chain:
input: invoice.json
Account.Order.Product.(
Price > 100 ? "premium" :
Price > 30 ? "standard" :
"budget"
)
=> ["standard", "budget", "standard", "premium"]
Transforms
The transform operator |location|update| or |location|update, delete| produces a function.
That function copies its argument and, in the copy, changes every object that location
matches:
updatemust evaluate to an object. Its fields are merged into each matched object, as$mergedoes.updateis evaluated against the matched object.delete(optional) names the fields to remove: a string or an array of strings.
The original value is not modified.
input: invoice.json
(
$copy := $ ~> |Account.Order.Product|{"Price": $round(Price * 1.2, 2)}, ["SKU", "Description"]|;
$copy.Account.Order[0].Product
)
=> [
{"Product Name": "Bowler Hat", "ProductID": 858383, "Price": 41.34, "Quantity": 2},
{"Product Name": "Trilby hat", "ProductID": 858236, "Price": 26, "Quantity": 1}
]
Updates see the original values. Here Total uses the price before the 20% increase:
{"Price": 10, "Quantity": 3} ~> |$|{"Price": Price * 1.2, "Total": Price * Quantity}|
=> {"Price": 12, "Quantity": 3, "Total": 30}
A transform is a function, so it can be bound to a variable and reused, or chained with other
transforms. An update that would make an object contain itself is an error (U1001) in jlens;
see Compatibility.
Comments and annotations
/* ... */ comments are ignored. jlens reads documentation comments (/** ... */) placed before
a subexpression as audit annotations: labels and messages that explain the
checks of a rule.