Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

SymbolType
bBoolean
nnumber
sstring
lnull
aarray; a<n> is an array of numbers
oobject
ffunction
(sn)a choice: string or number
u(bnsl): any simple value
j(bnsloa): any JSON value
xany 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:

  • update must evaluate to an object. Its fields are merged into each matched object, as $merge does. update is 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.