@gmag11/nodered-mcp-server 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +162 -0
- package/index.js +133 -0
- package/package.json +58 -0
- package/resources/skills/nodered-flow-builder/SKILL.md +659 -0
- package/resources/skills/nodered-flow-layout/SKILL.md +395 -0
- package/resources/skills/nodered-flowfuse-dashboard/SKILL.md +941 -0
- package/resources/skills/nodered-fundamentals/SKILL.md +323 -0
- package/resources/skills/nodered-jsonata/SKILL.md +1039 -0
- package/resources/skills/nodered-mustache/SKILL.md +588 -0
- package/resources/skills/nodered-node-reference/SKILL.md +1020 -0
- package/resources/skills/nodered-node-reference/examples/common.json +113 -0
- package/resources/skills/nodered-node-reference/examples/network.json +107 -0
- package/resources/skills/nodered-node-reference/examples/parser.json +147 -0
- package/resources/skills/nodered-node-reference/examples/sequence.json +141 -0
- package/resources/skills/nodered-node-reference/examples/storage.json +104 -0
- package/resources/skills/nodered-patterns/SKILL.md +414 -0
- package/resources/skills/nodered-patterns/examples/error-handler.json +72 -0
- package/resources/skills/nodered-patterns/examples/http-endpoint.json +42 -0
- package/resources/skills/nodered-patterns/examples/mqtt-subscriber.json +47 -0
- package/resources/skills/nodered-patterns/examples/timer-flow.json +50 -0
- package/resources/skills/nodered-subflows/SKILL.md +261 -0
- package/resources/skills/nodered-uibuilder/SKILL.md +500 -0
- package/src/auth/api-key-verifier.js +36 -0
- package/src/auth/composite-verifier.js +59 -0
- package/src/auth/config.js +106 -0
- package/src/auth/oauth-clients-store.js +107 -0
- package/src/auth/oauth-provider.js +149 -0
- package/src/auth/oauth-token-store.js +312 -0
- package/src/nodered/auth.js +158 -0
- package/src/nodered/client.js +199 -0
- package/src/nodered/comms-client.js +500 -0
- package/src/renderer/colors.js +161 -0
- package/src/renderer/geometry.js +115 -0
- package/src/renderer/html-builder.js +571 -0
- package/src/renderer/index.js +51 -0
- package/src/renderer/ir-builder.js +161 -0
- package/src/renderer/layout.js +126 -0
- package/src/renderer/mermaid-builder.js +109 -0
- package/src/renderer/svg-builder.js +228 -0
- package/src/schemas/responses.js +283 -0
- package/src/server.js +844 -0
- package/src/skills/loader.js +84 -0
- package/src/staging-store.js +258 -0
- package/src/tools/add-nodes-to-group.js +216 -0
- package/src/tools/connect-nodes.js +115 -0
- package/src/tools/constants.js +45 -0
- package/src/tools/create-flow.js +87 -0
- package/src/tools/create-node.js +126 -0
- package/src/tools/create-subflow-instance.js +123 -0
- package/src/tools/create-subflow.js +101 -0
- package/src/tools/delete-context.js +60 -0
- package/src/tools/delete-flow.js +81 -0
- package/src/tools/delete-group.js +116 -0
- package/src/tools/delete-node.js +73 -0
- package/src/tools/delete-subflow.js +103 -0
- package/src/tools/deploy.js +94 -0
- package/src/tools/disconnect-nodes.js +158 -0
- package/src/tools/export-flow.js +161 -0
- package/src/tools/export-subflow.js +78 -0
- package/src/tools/flow-utils.js +376 -0
- package/src/tools/get-config-nodes.js +86 -0
- package/src/tools/get-context.js +76 -0
- package/src/tools/get-flow-diagram.js +99 -0
- package/src/tools/get-flow-nodes.js +116 -0
- package/src/tools/get-flows.js +74 -0
- package/src/tools/get-node-detail.js +77 -0
- package/src/tools/get-node-type-detail.js +92 -0
- package/src/tools/get-palette-nodes.js +63 -0
- package/src/tools/get-staging-status.js +34 -0
- package/src/tools/get-subflow-detail.js +110 -0
- package/src/tools/get-subflows.js +105 -0
- package/src/tools/import-flow.js +310 -0
- package/src/tools/inject-message.js +117 -0
- package/src/tools/install-node.js +31 -0
- package/src/tools/read-debug-messages.js +155 -0
- package/src/tools/refresh-staging.js +62 -0
- package/src/tools/remove-nodes-from-group.js +162 -0
- package/src/tools/render-staging.js +69 -0
- package/src/tools/response-utils.js +42 -0
- package/src/tools/search-nodes.js +134 -0
- package/src/tools/uninstall-node.js +31 -0
- package/src/tools/update-flow.js +95 -0
- package/src/tools/update-group.js +77 -0
- package/src/tools/update-node.js +132 -0
- package/src/tools/update-subflow.js +84 -0
- package/src/transport/http.js +252 -0
- package/src/transport/stdio.js +16 -0
- package/src/transport/ws-server.js +223 -0
|
@@ -0,0 +1,1039 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodered-jsonata
|
|
3
|
+
description: >-
|
|
4
|
+
Comprehensive reference for the JSONata expression language (v2.2.0).
|
|
5
|
+
Covers syntax, path navigation, predicates, operators, all built-in function
|
|
6
|
+
categories (string, numeric, aggregation, boolean, array, object, date/time,
|
|
7
|
+
higher-order), programming constructs (variables, functions, recursion),
|
|
8
|
+
the processing model (sequences, flattening), and the JavaScript embedding API.
|
|
9
|
+
Includes Node-RED-specific usage notes for change, switch, and inject nodes.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# JSONata Expression Language Reference
|
|
13
|
+
|
|
14
|
+
Comprehensive reference for JSONata — a lightweight query and transformation language for JSON data. JSONata is **Node-RED's native expression language**, used extensively in the "change" node, "switch" node, and wherever JSONata expressions are evaluated against `msg` objects.
|
|
15
|
+
|
|
16
|
+
JSONata is inspired by XPath 3.1 location path semantics. It is a **superset of JSON** — any valid JSON document is also a valid JSONata expression. This property enables using JSON as a template for output and replacing parts with expressions.
|
|
17
|
+
|
|
18
|
+
> **Version:** JSONata 2.2.0 (the version bundled with current Node-RED releases)
|
|
19
|
+
> **Try it:** [try.jsonata.org](http://try.jsonata.org/)
|
|
20
|
+
> **Docs:** [docs.jsonata.org](https://docs.jsonata.org/)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Sample JSON Document
|
|
25
|
+
|
|
26
|
+
The following JSON document is used in examples throughout this reference:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"FirstName": "Fred",
|
|
31
|
+
"Surname": "Smith",
|
|
32
|
+
"Age": 28,
|
|
33
|
+
"Address": {
|
|
34
|
+
"Street": "Hursley Park",
|
|
35
|
+
"City": "Winchester",
|
|
36
|
+
"Postcode": "SO21 2JN"
|
|
37
|
+
},
|
|
38
|
+
"Phone": [
|
|
39
|
+
{ "type": "home", "number": "0203 544 1234" },
|
|
40
|
+
{ "type": "office", "number": "01962 001234" },
|
|
41
|
+
{ "type": "office", "number": "01962 001235" },
|
|
42
|
+
{ "type": "mobile", "number": "077 7700 1234" }
|
|
43
|
+
],
|
|
44
|
+
"Email": [
|
|
45
|
+
{ "type": "work", "address": ["fred.smith@my-work.com", "fsmith@my-work.com"] },
|
|
46
|
+
{ "type": "home", "address": ["freddy@my-social.com", "frederic.smith@very-serious.com"] }
|
|
47
|
+
],
|
|
48
|
+
"Other": {
|
|
49
|
+
"Over 18 ?": true,
|
|
50
|
+
"Misc": null,
|
|
51
|
+
"Alternative.Address": {
|
|
52
|
+
"Street": "Brick Lane",
|
|
53
|
+
"City": "London",
|
|
54
|
+
"Postcode": "E1 6RF"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Path Navigation
|
|
63
|
+
|
|
64
|
+
### Dot Notation — Object Fields
|
|
65
|
+
|
|
66
|
+
Navigate into nested objects using dot `.` separators. If a field is not found, the expression returns nothing (JavaScript `undefined`) — **no errors are thrown**.
|
|
67
|
+
|
|
68
|
+
| Expression | Result |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `Surname` | `"Smith"` |
|
|
71
|
+
| `Age` | `28` |
|
|
72
|
+
| `Address.City` | `"Winchester"` |
|
|
73
|
+
| `Other.Misc` | `null` |
|
|
74
|
+
| `Other.Nothing` | _(nothing)_ |
|
|
75
|
+
|
|
76
|
+
Fields containing whitespace or reserved tokens must be enclosed in backticks:
|
|
77
|
+
|
|
78
|
+
| Expression | Result |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `` Other.`Over 18 ?` `` | `true` |
|
|
81
|
+
| `` Other.`Alternative.Address`.City `` | `"London"` |
|
|
82
|
+
|
|
83
|
+
### Bracket Notation — Array Indexing
|
|
84
|
+
|
|
85
|
+
Use `[index]` to access array elements. Indexes are **zero-offset**. Negative indexes count from the end of the array. Non-integer indexes are rounded down. If an index exceeds the array size, nothing is selected.
|
|
86
|
+
|
|
87
|
+
| Expression | Result |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `Phone[0]` | `{ "type": "home", "number": "0203 544 1234" }` |
|
|
90
|
+
| `Phone[1]` | `{ "type": "office", "number": "01962 001234" }` |
|
|
91
|
+
| `Phone[-1]` | `{ "type": "mobile", "number": "077 7700 1234" }` |
|
|
92
|
+
| `Phone[-2]` | `{ "type": "office", "number": "01962 001235" }` |
|
|
93
|
+
| `Phone[8]` | _(nothing)_ |
|
|
94
|
+
|
|
95
|
+
**Array of indexes** selects a range:
|
|
96
|
+
|
|
97
|
+
| Expression | Result |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `Phone[[0..1]]` | First two phone objects |
|
|
100
|
+
|
|
101
|
+
> **Important:** `Phone.number` selects the `number` field from ALL items → `["0203 544 1234", "01962 001234", ...]`. Use `(Phone.number)[0]` to get just the first number.
|
|
102
|
+
|
|
103
|
+
### Wildcards
|
|
104
|
+
|
|
105
|
+
| Symbol | Meaning |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `*` | Selects all values of all properties in the context object |
|
|
108
|
+
| `**` | Descendant wildcard — recursively traverses all levels |
|
|
109
|
+
|
|
110
|
+
| Expression | Result |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `Address.*` | `["Hursley Park", "Winchester", "SO21 2JN"]` |
|
|
113
|
+
| `*.Postcode` | `"SO21 2JN"` |
|
|
114
|
+
| `**.Postcode` | `["SO21 2JN", "E1 6RF"]` |
|
|
115
|
+
|
|
116
|
+
### Context and Root References
|
|
117
|
+
|
|
118
|
+
| Symbol | Meaning |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `$` | The current context value at any point in the input hierarchy |
|
|
121
|
+
| `$$` | The root of the input JSON (breaks out of current context) |
|
|
122
|
+
|
|
123
|
+
| Expression | Result |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `$[0]` | First item in top-level array (when input is an array) |
|
|
126
|
+
| `$$.FirstName` | `"Fred"` (access root from anywhere) |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Predicates
|
|
131
|
+
|
|
132
|
+
Filter selected items using `[expr]` where `expr` evaluates to a Boolean. The expression is evaluated relative to the current context item.
|
|
133
|
+
|
|
134
|
+
| Expression | Result |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `Phone[type='mobile']` | The mobile phone object |
|
|
137
|
+
| `Phone[type='mobile'].number` | `"077 7700 1234"` |
|
|
138
|
+
| `Phone[type='office'].number` | `["01962 001234", "01962 001235"]` |
|
|
139
|
+
|
|
140
|
+
**Combining predicates with Boolean operators:**
|
|
141
|
+
|
|
142
|
+
| Expression | Meaning |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `Phone[type='home' or type='mobile'].number` | Home and mobile numbers |
|
|
145
|
+
|
|
146
|
+
### Singleton Array and Value Equivalence
|
|
147
|
+
|
|
148
|
+
Any non-array value and an array containing just that value are treated as equivalent. To **force an array result**, add `[]` at any step in the path:
|
|
149
|
+
|
|
150
|
+
| Expression | Result |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `Address.City` | `"Winchester"` (single value) |
|
|
153
|
+
| `Address[].City` | `["Winchester"]` (always an array) |
|
|
154
|
+
| `Phone[][type='home'].number` | `["0203 544 1234"]` |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Operators
|
|
159
|
+
|
|
160
|
+
### Numeric Operators
|
|
161
|
+
|
|
162
|
+
| Operator | Description | Example | Result |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| `+` | Addition | `5 + 2` | `7` |
|
|
165
|
+
| `-` | Subtraction / Negation | `5 - 2` / `-42` | `3` / `-42` |
|
|
166
|
+
| `*` | Multiplication | `5 * 2` | `10` |
|
|
167
|
+
| `/` | Division | `5 / 2` | `2.5` |
|
|
168
|
+
| `%` | Modulo (remainder) | `5 % 2` | `1` |
|
|
169
|
+
| `..` | Range generator (inside `[]`) | `[1..5]` | `[1, 2, 3, 4, 5]` |
|
|
170
|
+
|
|
171
|
+
Range examples:
|
|
172
|
+
- `[1..3, 7..9]` → `[1, 2, 3, 7, 8, 9]`
|
|
173
|
+
- `[1..$count(Items)].("Item " & $)` → dynamic numbered list
|
|
174
|
+
- `[1..5].($*$)` → `[1, 4, 9, 16, 25]`
|
|
175
|
+
|
|
176
|
+
### Comparison Operators
|
|
177
|
+
|
|
178
|
+
| Operator | Description | Example |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| `=` | Deep equality | `1+1 = 2` → `true` |
|
|
181
|
+
| `!=` | Not equals | `"Hello" != "World"` → `true` |
|
|
182
|
+
| `>` | Greater than | `22/7 > 3` → `true` |
|
|
183
|
+
| `<` | Less than | `22/7 < 3` → `false` |
|
|
184
|
+
| `>=` | Greater than or equals | `5 >= 5` → `true` |
|
|
185
|
+
| `<=` | Less than or equals | `5 <= 5` → `true` |
|
|
186
|
+
| `in` | Array inclusion | `"world" in ["hello", "world"]` → `true` |
|
|
187
|
+
|
|
188
|
+
> `in` treats a scalar RHS as a singleton array: `"hello" in "hello"` → `true`
|
|
189
|
+
|
|
190
|
+
### Boolean Operators
|
|
191
|
+
|
|
192
|
+
| Operator | Description | Example |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `and` | Boolean AND | `price < 50 and section="diy"` |
|
|
195
|
+
| `or` | Boolean OR | `price < 10 or section="diy"` |
|
|
196
|
+
|
|
197
|
+
> Non-Boolean operands are cast using `$boolean()` rules. Boolean NOT is the `$not()` function, not an operator.
|
|
198
|
+
|
|
199
|
+
### String Concatenation
|
|
200
|
+
|
|
201
|
+
| Operator | Description | Example | Result |
|
|
202
|
+
|---|---|---|---|
|
|
203
|
+
| `&` | String concatenation | `"Hello" & " " & "World"` | `"Hello World"` |
|
|
204
|
+
|
|
205
|
+
> Non-string operands are cast using `$string()` rules.
|
|
206
|
+
|
|
207
|
+
### Conditional Operators
|
|
208
|
+
|
|
209
|
+
| Operator | Syntax | Description |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| Ternary | `test ? exprT : exprF` | If `test` is true, evaluate `exprT`; else `exprF` |
|
|
212
|
+
| Elvis/Default | `expr ?: default` | Returns `expr` if its effective Boolean is `true`; else `default` |
|
|
213
|
+
| Coalescing | `expr ?? default` | Returns `expr` if it is defined (not `undefined`); else `default` |
|
|
214
|
+
|
|
215
|
+
```jsonata
|
|
216
|
+
Price < 50 ? "Cheap" : "Expensive"
|
|
217
|
+
foo.bar ?: 'default' -- fallback if falsy
|
|
218
|
+
foo.bar ?? 42 -- fallback only if undefined
|
|
219
|
+
0 ?: 1 -- 0 is falsy → 1
|
|
220
|
+
0 ?? 1 -- 0 is defined → 0
|
|
221
|
+
'' ?? 'fallback' -- '' is defined → ''
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Variable Binding, Chaining, and Transform
|
|
225
|
+
|
|
226
|
+
| Operator | Syntax | Description |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| Variable binding | `$var := expr` | Binds `expr` to `$var` (block-scoped) |
|
|
229
|
+
| Function chaining | `value ~> func` | Passes LHS as first argument to RHS function |
|
|
230
|
+
| Object transform | `` head ~> \| location \| update [, delete] \| `` | Deep-copies `head`, applies `update` to matched `location`(s) |
|
|
231
|
+
|
|
232
|
+
```jsonata
|
|
233
|
+
-- Variable binding
|
|
234
|
+
$five := 5
|
|
235
|
+
$square := function($n) { $n * $n }
|
|
236
|
+
|
|
237
|
+
-- Function chaining
|
|
238
|
+
Customer.Email ~> $substringAfter("@") ~> $substringBefore(".") ~> $uppercase()
|
|
239
|
+
|
|
240
|
+
-- Function composition via chaining
|
|
241
|
+
$uppertrim := $trim ~> $uppercase
|
|
242
|
+
$uppertrim(" Hello World ") -- "HELLO WORLD"
|
|
243
|
+
|
|
244
|
+
-- Object transform: increase all Product prices by 20%
|
|
245
|
+
payload ~> |Account.Order.Product|{'Price': Price * 1.2}|
|
|
246
|
+
|
|
247
|
+
-- Transform with property deletion
|
|
248
|
+
$ ~> |Account.Order.Product|{'Total': Price * Quantity}, ['Price', 'Quantity']|
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Path Operators
|
|
254
|
+
|
|
255
|
+
JSONata's path expression is a **declarative functional language** based on map/filter/reduce. Each stage in the path processes the result sequence from the previous stage.
|
|
256
|
+
|
|
257
|
+
| Stage | Syntax | Action |
|
|
258
|
+
|---|---|---|
|
|
259
|
+
| **Map** | `seq.expr` | Evaluates RHS expression in context of each item. Flattens results. |
|
|
260
|
+
| **Filter** | `seq[expr]` | Keeps items where predicate `expr` is `true` |
|
|
261
|
+
| **Order-by** | `seq^(expr)` | Sorts sequence by criteria (ascending default; `>` for descending) |
|
|
262
|
+
| **Reduce** | `seq{key: value, ...}` | Groups and aggregates into a single object |
|
|
263
|
+
| **Wildcard** | `*` | Selects all property values of context object |
|
|
264
|
+
| **Descendants** | `**` | Recursively selects all descendant values |
|
|
265
|
+
| **Parent** | `%` | Selects the enclosing object of the context value |
|
|
266
|
+
| **Position var** | `# $var` | Binds variable to current position (zero offset) in sequence |
|
|
267
|
+
| **Context var** | `@ $var` | Binds variable to current context item (only after map) |
|
|
268
|
+
|
|
269
|
+
### Order-by Examples
|
|
270
|
+
|
|
271
|
+
```jsonata
|
|
272
|
+
Account.Order.Product^(Price) -- ascending by price
|
|
273
|
+
Account.Order.Product^(>Price) -- descending by price
|
|
274
|
+
Account.Order.Product^(>Price, <Quantity) -- desc price, then asc quantity
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Parent Operator
|
|
278
|
+
|
|
279
|
+
```jsonata
|
|
280
|
+
Account.Order.Product.{
|
|
281
|
+
'Product': `Product Name`,
|
|
282
|
+
'Order': %.OrderID,
|
|
283
|
+
'Account': %.%.`Account Name`
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Context Variable Binding (Data Joins)
|
|
288
|
+
|
|
289
|
+
```jsonata
|
|
290
|
+
library.loans@$l.books@$b[$l.isbn=$b.isbn].{
|
|
291
|
+
'title': $b.title,
|
|
292
|
+
'customer': $l.customer
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Positional Variable Binding
|
|
297
|
+
|
|
298
|
+
```jsonata
|
|
299
|
+
library.books#$i['Kernighan' in authors].{
|
|
300
|
+
'title': title,
|
|
301
|
+
'index': $i
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Query Composition
|
|
308
|
+
|
|
309
|
+
In JSONata, **everything is an expression**. Expressions comprise values, functions and operators which, when evaluated, produce a resulting value. Functions and operators are applied to values which themselves can be the results of evaluating sub-expressions — the language is fully composable to any level of complexity.
|
|
310
|
+
|
|
311
|
+
### Parenthesized Expressions and Blocks
|
|
312
|
+
|
|
313
|
+
Parentheses `()` serve three purposes:
|
|
314
|
+
|
|
315
|
+
1. **Override operator precedence** — `(5 + 3) * 4` → `32`
|
|
316
|
+
2. **Compute complex expressions on a context value** — `Product.(Price * Quantity)` — both `Price` and `Quantity` are resolved relative to each `Product`
|
|
317
|
+
3. **Code blocks** — multiple expressions separated by semicolons; the last expression's result is returned:
|
|
318
|
+
|
|
319
|
+
```jsonata
|
|
320
|
+
(
|
|
321
|
+
$pi := 3.14159;
|
|
322
|
+
$r := 5;
|
|
323
|
+
$pi * $r * $r -- returned result
|
|
324
|
+
)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Blocks also define a **scope frame** for variables. Variables bound inside parentheses go out of scope when the block ends.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Processing Model
|
|
332
|
+
|
|
333
|
+
### Type System
|
|
334
|
+
|
|
335
|
+
JSONata supports 7 types:
|
|
336
|
+
- `string`, `number`, `boolean`, `null`, `object`, `array`, `function`
|
|
337
|
+
|
|
338
|
+
### Sequence Flattening Rules
|
|
339
|
+
|
|
340
|
+
1. **Empty sequence** = "nothing" (won't appear in output, won't create object properties)
|
|
341
|
+
2. **Singleton sequence** = equivalent to the value itself
|
|
342
|
+
3. **Multi-value sequence** = represented as a JSON array in output
|
|
343
|
+
4. **Nested sequences** = flattened (child values pulled up to parent level)
|
|
344
|
+
|
|
345
|
+
> **Key difference:** An array from input JSON or `[...]` constructor is an **array value**, not a sequence. It won't be flattened unless it becomes context for a subsequent expression.
|
|
346
|
+
|
|
347
|
+
### Path Processing Stages
|
|
348
|
+
|
|
349
|
+
| Stage | Syntax | Precedence |
|
|
350
|
+
|---|---|---|
|
|
351
|
+
| Map | `seq.expr` | Lower than Filter |
|
|
352
|
+
| Filter | `seq[expr]` | Binds tighter than Map |
|
|
353
|
+
| Sort (Order-by) | `seq^(expr)` | Lowest precedence |
|
|
354
|
+
| Index | `seq#$var` | — |
|
|
355
|
+
| Join (Context bind) | `seq@$var` | After a Map stage only |
|
|
356
|
+
| Reduce | `seq{...}` | Terminates the path expression |
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## String Functions
|
|
361
|
+
|
|
362
|
+
All string functions with signatures. Functions with `-` in their signature can be invoked without an explicit first argument, using the context value instead.
|
|
363
|
+
|
|
364
|
+
| Function | Signature | Description |
|
|
365
|
+
|---|---|---|
|
|
366
|
+
| `$string()` | `<x-:s>` | Cast to string (JSON.stringify rules) |
|
|
367
|
+
| `$length()` | `<s-:n>` | Character count |
|
|
368
|
+
| `$substring()` | `<s-n?:s>` | `$substring(str, start[, length])` |
|
|
369
|
+
| `$substringBefore()` | `<s-s:s>` | Substring before first occurrence of chars |
|
|
370
|
+
| `$substringAfter()` | `<s-s:s>` | Substring after first occurrence of chars |
|
|
371
|
+
| `$uppercase()` | `<s-:s>` | Convert to uppercase |
|
|
372
|
+
| `$lowercase()` | `<s-:s>` | Convert to lowercase |
|
|
373
|
+
| `$trim()` | `<s-:s>` | Normalize whitespace |
|
|
374
|
+
| `$pad()` | `<sn?s?:s>` | `$pad(str, width[, char])` — right pad if width>0, left if <0 |
|
|
375
|
+
| `$contains()` | `<s-s:s>` | `true` if `str` contains `pattern` (string or regex) |
|
|
376
|
+
| `$split()` | `<s-sn?:a<s>>` | `$split(str, separator[, limit])` |
|
|
377
|
+
| `$join()` | `<a<s>s?:s>` | Join array with optional separator |
|
|
378
|
+
| `$match()` | `<s-sn?:a>` | Returns array of match objects `{match, index, groups}` |
|
|
379
|
+
| `$replace()` | `<s-s<sf>n?:s>` | Replace `pattern` with `replacement` (string/function) |
|
|
380
|
+
| `$eval()` | `<ss?:x>` | Parse and evaluate string as JSONata |
|
|
381
|
+
| `$base64encode()` | `<s-:s>` | Base64 encode ASCII string |
|
|
382
|
+
| `$base64decode()` | `<s-:s>` | Base64 decode to UTF-8 string |
|
|
383
|
+
| `$encodeUrlComponent()` | `<s-:s>` | URL-encode component |
|
|
384
|
+
| `$encodeUrl()` | `<s-:s>` | URL-encode full URL |
|
|
385
|
+
| `$decodeUrlComponent()` | `<s-:s>` | URL-decode component |
|
|
386
|
+
| `$decodeUrl()` | `<s-:s>` | URL-decode full URL |
|
|
387
|
+
|
|
388
|
+
### Regex Usage with String Functions
|
|
389
|
+
|
|
390
|
+
```jsonata
|
|
391
|
+
-- Contains with regex
|
|
392
|
+
$contains("abracadabra", /a.*a/) -- true
|
|
393
|
+
$contains("Hello World", /wo/i) -- true (case-insensitive)
|
|
394
|
+
|
|
395
|
+
-- Split with regex
|
|
396
|
+
$split("too much, punctuation. hard; to read", /[ ,.;]+/)
|
|
397
|
+
-- ["too", "much", "punctuation", "hard", "to", "read"]
|
|
398
|
+
|
|
399
|
+
-- Match with regex (returns match objects)
|
|
400
|
+
$match("ababbabbcc", /a(b+)/)
|
|
401
|
+
-- [{"match":"ab","index":0,"groups":["b"]}, ...]
|
|
402
|
+
|
|
403
|
+
-- Replace with regex and captured groups
|
|
404
|
+
$replace("John Smith", /(\w+)\s(\w+)/, "$2, $1")
|
|
405
|
+
-- "Smith, John"
|
|
406
|
+
|
|
407
|
+
-- Replace with function (temperature conversion)
|
|
408
|
+
$replace("temperature = 68F today", /(\d+)F/, function($m) {
|
|
409
|
+
($number($m.groups[0]) - 32) * 5/9 & "C"
|
|
410
|
+
})
|
|
411
|
+
-- "temperature = 20C today"
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Regular Expressions
|
|
417
|
+
|
|
418
|
+
JSONata provides first-class support for regular expressions using the familiar slash delimiter syntax:
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
/regex/flags
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Where `flags` can be:
|
|
425
|
+
- `i` — case-insensitive matching
|
|
426
|
+
- `m` — multiline matching (both can be combined: `/regex/im`)
|
|
427
|
+
|
|
428
|
+
> **Key insight:** `/regex/` evaluates to a **function** — a "matcher function". When invoked on a string, it returns a match object with `{match, start, end, groups, next()}`. The `next()` function advances to the next match.
|
|
429
|
+
|
|
430
|
+
### Regex-Aware Functions
|
|
431
|
+
|
|
432
|
+
Four built-in functions accept regex patterns: `$match()`, `$contains()`, `$split()`, `$replace()`.
|
|
433
|
+
|
|
434
|
+
### Regex in Query Predicates (Shortcut)
|
|
435
|
+
|
|
436
|
+
Use the chain operator `~>` for concise predicate matching:
|
|
437
|
+
|
|
438
|
+
```jsonata
|
|
439
|
+
Account.Order.Product[`Product Name` ~> /hat/i]
|
|
440
|
+
-- Matches all products with 'hat' in their name (case-insensitive)
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
The `~>` operator passes the field value to the regex matcher function, returning `true`/`false`.
|
|
444
|
+
|
|
445
|
+
### Generic Matcher Functions
|
|
446
|
+
|
|
447
|
+
Since `/regex/` is just a function generator, you can bind regexes to variables and invoke them:
|
|
448
|
+
|
|
449
|
+
```jsonata
|
|
450
|
+
$matcher := /[a-z]*an[a-z]*/i
|
|
451
|
+
$matcher('A man, a plan, a canal, Panama!')
|
|
452
|
+
-- {"match": "man", "start": 2, "end": 4, "groups": [], "next": <function>}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Custom Matchers
|
|
456
|
+
|
|
457
|
+
The regex-aware functions work with any function conforming to the matcher contract. You can write custom matchers as JSONata lambdas or extension functions and pass them to `$match`, `$contains`, `$split`, and `$replace`.
|
|
458
|
+
|
|
459
|
+
### Other String Examples
|
|
460
|
+
|
|
461
|
+
```jsonata
|
|
462
|
+
$substring("Hello World", 3) -- "lo World"
|
|
463
|
+
$substring("Hello World", 3, 5) -- "lo Wo"
|
|
464
|
+
$substring("Hello World", -4) -- "orld"
|
|
465
|
+
$pad("foo", 5) -- "foo "
|
|
466
|
+
$pad("foo", -5, "#") -- "##foo"
|
|
467
|
+
$trim(" Hello \n World ") -- "Hello World"
|
|
468
|
+
$eval("[1,$string(2),3]") -- [1, "2", 3]
|
|
469
|
+
$base64encode("myuser:mypass") -- "bXl1c2VyOm15cGFzcw=="
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Numeric Functions
|
|
475
|
+
|
|
476
|
+
| Function | Signature | Description |
|
|
477
|
+
|---|---|---|
|
|
478
|
+
| `$number()` | `<x-:n>` | Cast to number (supports hex `0x`, octal `0o`, binary `0b`) |
|
|
479
|
+
| `$abs()` | `<n-:n>` | Absolute value |
|
|
480
|
+
| `$floor()` | `<n-:n>` | Round down to nearest integer |
|
|
481
|
+
| `$ceil()` | `<n-:n>` | Round up to nearest integer |
|
|
482
|
+
| `$round()` | `<nn?:n>` | Round to `precision` decimal places (default 0) |
|
|
483
|
+
| `$power()` | `<nn-:n>` | `base` raised to `exponent` |
|
|
484
|
+
| `$sqrt()` | `<n-:n>` | Square root |
|
|
485
|
+
| `$random()` | `<>:n` | Pseudo-random number 0 ≤ n < 1 |
|
|
486
|
+
| `$formatNumber()` | `<ns-:s>` | Format number using XPath picture string |
|
|
487
|
+
| `$formatBase()` | `<nn?:s>` | Format integer in given radix (2-36) |
|
|
488
|
+
| `$formatInteger()` | `<ns-:s>` | Format integer using XPath picture string |
|
|
489
|
+
| `$parseInteger()` | `<ss-:n>` | Parse formatted integer string to number |
|
|
490
|
+
|
|
491
|
+
### Rounding Semantics
|
|
492
|
+
|
|
493
|
+
`$round()` uses **round half to even** (banker's rounding), the default IEEE 754 mode:
|
|
494
|
+
|
|
495
|
+
```jsonata
|
|
496
|
+
$round(123.456) -- 123
|
|
497
|
+
$round(123.456, 2) -- 123.46
|
|
498
|
+
$round(123.456, -1) -- 120 (round to tens)
|
|
499
|
+
$round(11.5) -- 12 (half → even)
|
|
500
|
+
$round(12.5) -- 12 (half → even)
|
|
501
|
+
$round(125, -1) -- 120 (half → even)
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Number Formatting
|
|
505
|
+
|
|
506
|
+
```jsonata
|
|
507
|
+
$formatNumber(12345.6, '#,###.00') -- "12,345.60"
|
|
508
|
+
$formatNumber(1234.5678, "00.000e0") -- "12.346e2"
|
|
509
|
+
$formatNumber(-34.555, "#0.00;(#0.00)") -- "(34.56)"
|
|
510
|
+
$formatNumber(0.14, "01%") -- "14%"
|
|
511
|
+
$formatBase(100, 2) -- "1100100"
|
|
512
|
+
$formatBase(2555, 16) -- "9fb"
|
|
513
|
+
$formatInteger(2789, 'w') -- "two thousand, seven hundred and eighty-nine"
|
|
514
|
+
$formatInteger(1999, 'I') -- "MCMXCIX"
|
|
515
|
+
$parseInteger("twelve thousand, four hundred and seventy-six", 'w') -- 12476
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## Aggregation Functions
|
|
521
|
+
|
|
522
|
+
| Function | Signature | Description |
|
|
523
|
+
|---|---|---|
|
|
524
|
+
| `$sum()` | `<a<n>:n>` | Arithmetic sum of array of numbers |
|
|
525
|
+
| `$max()` | `<a<n>:n>` | Maximum number in array |
|
|
526
|
+
| `$min()` | `<a<n>:n>` | Minimum number in array |
|
|
527
|
+
| `$average()` | `<a<n>:n>` | Mean value of array of numbers |
|
|
528
|
+
|
|
529
|
+
```jsonata
|
|
530
|
+
$sum([5, 1, 3, 7, 4]) -- 20
|
|
531
|
+
$max([5, 1, 3, 7, 4]) -- 7
|
|
532
|
+
$min([5, 1, 3, 7, 4]) -- 1
|
|
533
|
+
$average([5, 1, 3, 7, 4]) -- 4
|
|
534
|
+
|
|
535
|
+
-- In path expressions
|
|
536
|
+
$sum(Account.Order.Product.(Price*Quantity)) -- total of all price×quantity
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Boolean Functions
|
|
542
|
+
|
|
543
|
+
| Function | Signature | Description |
|
|
544
|
+
|---|---|---|
|
|
545
|
+
| `$boolean()` | `<x-:b>` | Cast to Boolean |
|
|
546
|
+
| `$not()` | `<x-:b>` | Boolean NOT (argument cast first) |
|
|
547
|
+
| `$exists()` | `<x-:b>` | `true` if expression evaluates to a value |
|
|
548
|
+
|
|
549
|
+
### `$boolean()` Casting Rules
|
|
550
|
+
|
|
551
|
+
| Value | Result |
|
|
552
|
+
|---|---|
|
|
553
|
+
| Boolean | unchanged |
|
|
554
|
+
| string: empty `""` | `false` |
|
|
555
|
+
| string: non-empty | `true` |
|
|
556
|
+
| number: `0` | `false` |
|
|
557
|
+
| number: non-zero | `true` |
|
|
558
|
+
| `null` | `false` |
|
|
559
|
+
| array: empty | `false` |
|
|
560
|
+
| array: contains member that casts to `true` | `true` |
|
|
561
|
+
| array: all members cast to `false` | `false` |
|
|
562
|
+
| object: empty | `false` |
|
|
563
|
+
| object: non-empty | `true` |
|
|
564
|
+
| function | `false` |
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## Array Functions
|
|
569
|
+
|
|
570
|
+
| Function | Signature | Description |
|
|
571
|
+
|---|---|---|
|
|
572
|
+
| `$count()` | `<x-:n>` | Number of items (non-arrays count as 1) |
|
|
573
|
+
| `$append()` | `<xx:a>` | Concatenate two arrays (or values) |
|
|
574
|
+
| `$sort()` | `<a<f>?:a>` | Sort array; optional comparator `function($l, $r)` |
|
|
575
|
+
| `$reverse()` | `<a:a>` | Reverse array order |
|
|
576
|
+
| `$shuffle()` | `<a:a>` | Random shuffle |
|
|
577
|
+
| `$distinct()` | `<a:a>` | Remove duplicates (deep equality) |
|
|
578
|
+
| `$zip()` | `<a+>` | Convolve arrays: `$zip([1,2,3], [4,5,6])` → `[[1,4],[2,5],[3,6]]` |
|
|
579
|
+
|
|
580
|
+
```jsonata
|
|
581
|
+
$count([1,2,3,1]) -- 4
|
|
582
|
+
$append([1,2,3], [4,5,6]) -- [1,2,3,4,5,6]
|
|
583
|
+
$reverse(["Hello", "World"]) -- ["World", "Hello"]
|
|
584
|
+
$shuffle([1..9]) -- [6, 8, 2, 3, 9, 5, 1, 4, 7] (random)
|
|
585
|
+
$distinct([1,2,3,3,4,3,5]) -- [1, 2, 3, 4, 5]
|
|
586
|
+
$zip([1,2,3], [4,5], [7,8,9]) -- [[1,4,7], [2,5,8]] (shortest array wins)
|
|
587
|
+
|
|
588
|
+
-- Custom sort comparator
|
|
589
|
+
$sort(Account.Order.Product, function($l, $r) {
|
|
590
|
+
$l.Description.Weight > $r.Description.Weight
|
|
591
|
+
})
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
## Object Functions
|
|
597
|
+
|
|
598
|
+
| Function | Signature | Description |
|
|
599
|
+
|---|---|---|
|
|
600
|
+
| `$keys()` | `<o-:a<s>>` | Array of keys in object (de-duplicated for arrays of objects) |
|
|
601
|
+
| `$lookup()` | `<os-:x>` | Value for key in object(s) |
|
|
602
|
+
| `$spread()` | `<o-:a<o>>` | Split object into array of single-key objects |
|
|
603
|
+
| `$merge()` | `<a<o>:o>` | Merge array of objects (last key wins on conflict) |
|
|
604
|
+
| `$each()` | `<of:a>` | Apply function to each key/value pair |
|
|
605
|
+
| `$error()` | `<s-:>` | Throw error with message |
|
|
606
|
+
| `$assert()` | `<bs-:>` | Throw error if condition is `false` |
|
|
607
|
+
| `$type()` | `<x-:s>` | Return type string: "null", "number", "string", "boolean", "array", "object", "function" |
|
|
608
|
+
|
|
609
|
+
```jsonata
|
|
610
|
+
$keys(Address) -- ["Street", "City", "Postcode"]
|
|
611
|
+
$lookup(Address, "City") -- "Winchester"
|
|
612
|
+
$merge([{"a":1},{"b":2},{"a":3}]) -- {"a":3, "b":2}
|
|
613
|
+
$each(Address, function($v, $k) { $k & ": " & $v })
|
|
614
|
+
-- ["Street: Hursley Park", "City: Winchester", "Postcode: SO21 2JN"]
|
|
615
|
+
$type("hello") -- "string"
|
|
616
|
+
$type([1,2,3]) -- "array"
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## Date/Time Functions
|
|
622
|
+
|
|
623
|
+
JSON does not have a date type. The convention is **ISO 8601 string format** (`"2018-12-10T13:45:00.000Z"`). JSONata follows this convention.
|
|
624
|
+
|
|
625
|
+
| Function | Signature | Description |
|
|
626
|
+
|---|---|---|
|
|
627
|
+
| `$now()` | `<s?s?:s>` | Current UTC timestamp (same value for all calls in one expression) |
|
|
628
|
+
| `$millis()` | `<>:n` | Milliseconds since Unix epoch |
|
|
629
|
+
| `$fromMillis()` | `<ns?s?:s>` | Format milliseconds to string using optional picture string |
|
|
630
|
+
| `$toMillis()` | `<ss?:n>` | Parse timestamp string to milliseconds using optional picture string |
|
|
631
|
+
|
|
632
|
+
### Picture String Notation
|
|
633
|
+
|
|
634
|
+
Uses XPath F&O 3.1 `fn:format-dateTime` picture string syntax. Common components:
|
|
635
|
+
|
|
636
|
+
| Component | Meaning | Example |
|
|
637
|
+
|---|---|---|
|
|
638
|
+
| `[Y]` / `[Y0001]` | Year | `2018` |
|
|
639
|
+
| `[M]` / `[M01]` | Month | `12` / `05` |
|
|
640
|
+
| `[D]` / `[D01]` | Day | `10` / `05` |
|
|
641
|
+
| `[H]` / `[H01]` / `[h#1]` | Hour (24h / 24h padded / 12h) | `13` / `13` / `1pm` |
|
|
642
|
+
| `[m]` / `[m01]` | Minute | `45` / `05` |
|
|
643
|
+
| `[s]` / `[s01]` | Second | `00` / `05` |
|
|
644
|
+
| `[P]` | AM/PM marker | `pm` |
|
|
645
|
+
| `[z]` | Timezone | `GMT-05:00` |
|
|
646
|
+
| `[FNn]` | Full day name | `Monday` |
|
|
647
|
+
| `[MNn]` | Full month name | `December` |
|
|
648
|
+
|
|
649
|
+
```jsonata
|
|
650
|
+
$now() -- "2017-05-15T15:12:59.152Z"
|
|
651
|
+
$millis() -- 1502700297574
|
|
652
|
+
$fromMillis(1510067557121) -- "2017-11-07T15:12:37.121Z"
|
|
653
|
+
$fromMillis(1510067557121, '[M01]/[D01]/[Y0001] [h#1]:[m01][P]') -- "11/07/2017 3:12pm"
|
|
654
|
+
$fromMillis(1510067557121, '[H01]:[m01]:[s01] [z]', '-0500') -- "10:12:37 GMT-05:00"
|
|
655
|
+
$toMillis("2017-11-07T15:07:54.972Z") -- 1510067274972
|
|
656
|
+
$toMillis('10/12/2018', '[D]/[M]/[Y]') -- parse EU format
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## Higher-Order Functions
|
|
662
|
+
|
|
663
|
+
Functions that take functions as arguments.
|
|
664
|
+
|
|
665
|
+
| Function | Signature | Description |
|
|
666
|
+
|---|---|---|
|
|
667
|
+
| `$map()` | `<af<a?>?:a>` | Apply function to each array item |
|
|
668
|
+
| `$filter()` | `<af<a?>?:a>` | Keep items where function returns `true` |
|
|
669
|
+
| `$single()` | `<af<a?>?:x>` | Return the single item matching predicate; error if not exactly one |
|
|
670
|
+
| `$reduce()` | `<af<*?>f?:x>` | Fold/accumulate: `$reduce(array, function($acc, $v, $i, $a)[, init])` |
|
|
671
|
+
| `$sift()` | `<of<o?>?:o>` | Keep object key/value pairs where function returns `true` |
|
|
672
|
+
|
|
673
|
+
### Callback Signatures
|
|
674
|
+
|
|
675
|
+
All array callbacks: `function(value [, index [, array]])`
|
|
676
|
+
|
|
677
|
+
`$sift` callback: `function(value [, key [, object]])`
|
|
678
|
+
|
|
679
|
+
### Examples
|
|
680
|
+
|
|
681
|
+
```jsonata
|
|
682
|
+
-- Map
|
|
683
|
+
$map([1,2,3], function($v) { $v * 2 }) -- [2, 4, 6]
|
|
684
|
+
|
|
685
|
+
-- Filter
|
|
686
|
+
$filter(Account.Order.Product, function($v, $i, $a) {
|
|
687
|
+
$v.Price > $average($a.Price)
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
-- Single
|
|
691
|
+
$single(Account.Order.Product, function($v) {
|
|
692
|
+
$v.SKU = "0406654608"
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
-- Reduce (product of all numbers)
|
|
696
|
+
$reduce([1..5], function($i, $j){ $i * $j }) -- 120
|
|
697
|
+
|
|
698
|
+
-- Sift (keep only keys starting with "Product")
|
|
699
|
+
Account.Order.Product.$sift(function($v, $k) { $k ~> /^Product/ })
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
## Programming Constructs
|
|
705
|
+
|
|
706
|
+
### Comments
|
|
707
|
+
|
|
708
|
+
```jsonata
|
|
709
|
+
/* Single or multi-line comments using C-style delimiters */
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### Conditional Logic
|
|
713
|
+
|
|
714
|
+
Three conditional forms:
|
|
715
|
+
- **Ternary:** `test ? exprT : exprF`
|
|
716
|
+
- **Elvis (Default):** `expr ?: default` — equivalent to `expr ? expr : default`
|
|
717
|
+
- **Coalescing:** `expr ?? default` — equivalent to `$exists(expr) ? expr : default`
|
|
718
|
+
|
|
719
|
+
```jsonata
|
|
720
|
+
Account.Order.Product.{
|
|
721
|
+
`Product Name`: $.Price > 100 ? "Premium" : "Basic",
|
|
722
|
+
`Category`: $.Category ?: "Uncategorized"
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Variables
|
|
727
|
+
|
|
728
|
+
Variable names start with `$`. Two built-in variables: `$` (context) and `$$` (root).
|
|
729
|
+
|
|
730
|
+
Variable binding uses `:=`, scoped to the current block:
|
|
731
|
+
|
|
732
|
+
```jsonata
|
|
733
|
+
(
|
|
734
|
+
$pi := 3.14159;
|
|
735
|
+
$r := 5;
|
|
736
|
+
$pi * $r * $r
|
|
737
|
+
)
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### Functions
|
|
741
|
+
|
|
742
|
+
**Lambda definition:**
|
|
743
|
+
```jsonata
|
|
744
|
+
function($l, $w, $h) { $l * $w * $h }
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
**Assign and invoke:**
|
|
748
|
+
```jsonata
|
|
749
|
+
(
|
|
750
|
+
$volume := function($l, $w, $h) { $l * $w * $h };
|
|
751
|
+
$volume(10, 10, 5) -- 500
|
|
752
|
+
)
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Function Signatures
|
|
756
|
+
|
|
757
|
+
Optional type annotations for parameter validation:
|
|
758
|
+
|
|
759
|
+
```jsonata
|
|
760
|
+
<params:return>
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
| Symbol | Type |
|
|
764
|
+
|---|---|
|
|
765
|
+
| `b` | Boolean |
|
|
766
|
+
| `n` | number |
|
|
767
|
+
| `s` | string |
|
|
768
|
+
| `l` | null |
|
|
769
|
+
| `a` | array |
|
|
770
|
+
| `o` | object |
|
|
771
|
+
| `f` | function |
|
|
772
|
+
| `u` | (bnsl) — Boolean, number, string, null |
|
|
773
|
+
| `j` | (bnsloa) — any JSON type |
|
|
774
|
+
| `x` | (bnsloaf) — any type |
|
|
775
|
+
| `a<s>` | array of strings |
|
|
776
|
+
| `+` | one or more arguments of this type |
|
|
777
|
+
| `?` | optional argument |
|
|
778
|
+
| `-` | use context if missing |
|
|
779
|
+
|
|
780
|
+
Examples of built-in signatures:
|
|
781
|
+
- `$count` → `<a:n>`
|
|
782
|
+
- `$sum` → `<a<n>:n>`
|
|
783
|
+
- `$reduce` → `<fa<j>:j>` (takes function + array of JSON, returns JSON)
|
|
784
|
+
|
|
785
|
+
### Recursion and Tail Call Optimization
|
|
786
|
+
|
|
787
|
+
```jsonata
|
|
788
|
+
-- Standard recursive factorial
|
|
789
|
+
$factorial := function($x) { $x <= 1 ? 1 : $x * $factorial($x-1) }
|
|
790
|
+
|
|
791
|
+
-- Tail-recursive factorial (optimized to a loop)
|
|
792
|
+
$factorial := function($x) {(
|
|
793
|
+
$iter := function($x, $acc) {
|
|
794
|
+
$x <= 1 ? $acc : $iter($x - 1, $x * $acc)
|
|
795
|
+
};
|
|
796
|
+
$iter($x, 1)
|
|
797
|
+
)}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
> Tail-recursive functions run in constant stack space and won't overflow.
|
|
801
|
+
|
|
802
|
+
### Closures
|
|
803
|
+
|
|
804
|
+
Functions capture their defining environment (context value + variable bindings):
|
|
805
|
+
|
|
806
|
+
```jsonata
|
|
807
|
+
Account.(
|
|
808
|
+
$AccName := function() { $.'Account Name' };
|
|
809
|
+
Order[OrderID = 'order104'].Product.{
|
|
810
|
+
'Account': $AccName(),
|
|
811
|
+
'SKU-' & $string(ProductID): $.'Product Name'
|
|
812
|
+
}
|
|
813
|
+
)
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
### Partial Function Application
|
|
817
|
+
|
|
818
|
+
Replace arguments with `?` to create new functions:
|
|
819
|
+
|
|
820
|
+
```jsonata
|
|
821
|
+
$first5 := $substring(?, 0, 5)
|
|
822
|
+
$first5("Hello, World") -- "Hello"
|
|
823
|
+
|
|
824
|
+
-- Chaining partial applications
|
|
825
|
+
$firstN := $substring(?, 0, ?)
|
|
826
|
+
$first5 := $firstN(?, 5)
|
|
827
|
+
|
|
828
|
+
-- Function composition with partials
|
|
829
|
+
$first5Capitalized := $substring(?, 0, 5) ~> $uppercase(?)
|
|
830
|
+
$first5Capitalized(Address.City) -- "WINCH"
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### Functions as First-Class Values
|
|
834
|
+
|
|
835
|
+
```jsonata
|
|
836
|
+
$twice := function($f) { function($x) { $f($f($x)) } }
|
|
837
|
+
$add3 := function($y) { $y + 3 }
|
|
838
|
+
$add6 := $twice($add3)
|
|
839
|
+
$add6(7) -- 13
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
---
|
|
843
|
+
|
|
844
|
+
## Object Construction and Grouping
|
|
845
|
+
|
|
846
|
+
### Array Constructors
|
|
847
|
+
|
|
848
|
+
Use `[...]` to build arrays. Commas separate expressions:
|
|
849
|
+
|
|
850
|
+
```jsonata
|
|
851
|
+
[Address, Other.`Alternative.Address`].City -- ["Winchester", "London"]
|
|
852
|
+
Email.[address] -- preserves nested structure per email object
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### Object Constructors
|
|
856
|
+
|
|
857
|
+
Use `{key: value, ...}` to build objects. Keys can be expressions that evaluate to strings:
|
|
858
|
+
|
|
859
|
+
```jsonata
|
|
860
|
+
Phone.{type: number}
|
|
861
|
+
-- [{"home":"0203 544 1234"},{"office":"01962 001234"}, ...]
|
|
862
|
+
|
|
863
|
+
Phone{type: number}
|
|
864
|
+
-- {"home":"0203 544 1234","office":["01962 001234","01962 001235"],"mobile":"077 7700 1234"}
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
> **Key difference:** `Phone.{type: number}` (with dot) creates one object per phone. `Phone{type: number}` (without dot) groups into a single object.
|
|
868
|
+
|
|
869
|
+
### Grouping with Object Key Expressions
|
|
870
|
+
|
|
871
|
+
The object constructor is also the **group-by** mechanism. The key expression determines grouping; value expressions aggregate:
|
|
872
|
+
|
|
873
|
+
```jsonata
|
|
874
|
+
-- Group products by name, collect prices
|
|
875
|
+
Account.Order.Product{`Product Name`: Price}
|
|
876
|
+
-- {"Bowler Hat":[34.45,34.45],"Trilby hat":21.67,"Cloak":107.99}
|
|
877
|
+
|
|
878
|
+
-- Group with aggregated total per product
|
|
879
|
+
Account.Order.Product{`Product Name`: $sum($.(Price*Quantity))}
|
|
880
|
+
-- {"Bowler Hat":206.7,"Trilby hat":21.67,"Cloak":107.99}
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### JSON Literals
|
|
884
|
+
|
|
885
|
+
JSONata is a superset of JSON — you can use a JSON document as a template:
|
|
886
|
+
|
|
887
|
+
```jsonata
|
|
888
|
+
{"name": FirstName & " " & Surname, "age": Age, "city": Address.City}
|
|
889
|
+
-- {"name":"Fred Smith","age":28,"city":"Winchester"}
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
---
|
|
893
|
+
|
|
894
|
+
## JavaScript Embedding API
|
|
895
|
+
|
|
896
|
+
For embedding JSONata in Node.js applications (reference implementation).
|
|
897
|
+
|
|
898
|
+
### Installation
|
|
899
|
+
|
|
900
|
+
```bash
|
|
901
|
+
npm install jsonata
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### Basic Usage
|
|
905
|
+
|
|
906
|
+
```javascript
|
|
907
|
+
const jsonata = require('jsonata');
|
|
908
|
+
|
|
909
|
+
const data = {
|
|
910
|
+
example: [
|
|
911
|
+
{value: 4},
|
|
912
|
+
{value: 7},
|
|
913
|
+
{value: 13}
|
|
914
|
+
]
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
(async () => {
|
|
918
|
+
const expression = jsonata('$sum(example.value)');
|
|
919
|
+
const result = await expression.evaluate(data); // 24
|
|
920
|
+
})();
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### API Reference
|
|
924
|
+
|
|
925
|
+
#### `jsonata(str[, options])`
|
|
926
|
+
Parse and compile an expression. Returns an expression object. Throws on syntax errors.
|
|
927
|
+
|
|
928
|
+
#### `expression.evaluate(input[, bindings[, callback]])`
|
|
929
|
+
Evaluate against `input` data. Returns a Promise resolving to the result.
|
|
930
|
+
|
|
931
|
+
```javascript
|
|
932
|
+
await jsonata("$a + $b()").evaluate({}, {a: 4, b: () => 78});
|
|
933
|
+
// 82
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
#### `expression.assign(name, value)`
|
|
937
|
+
Permanently bind a value to a name in the expression.
|
|
938
|
+
|
|
939
|
+
```javascript
|
|
940
|
+
expression.assign("a", 4);
|
|
941
|
+
expression.assign("b", () => 1);
|
|
942
|
+
await expression.evaluate({}); // 5
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
#### `expression.registerFunction(name, implementation[, signature])`
|
|
946
|
+
Register a custom function with optional type signature:
|
|
947
|
+
|
|
948
|
+
```javascript
|
|
949
|
+
expression.registerFunction("add", (a, b) => a + b, "<nn:n>");
|
|
950
|
+
await expression.evaluate({}); // 10066
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
### Guardrails
|
|
954
|
+
|
|
955
|
+
Protect against runaway expressions with configurable limits:
|
|
956
|
+
|
|
957
|
+
```javascript
|
|
958
|
+
const options = {
|
|
959
|
+
stack: 500, // max eval-apply cycle depth
|
|
960
|
+
timeout: 1000, // max execution time in ms
|
|
961
|
+
sequence: 1e6, // max sequence length
|
|
962
|
+
RegexEngine: SafeRegExp // custom regex processor for ReDoS protection
|
|
963
|
+
};
|
|
964
|
+
const expression = jsonata('<expr>', options);
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
| Guardrail | Error Code | Description |
|
|
968
|
+
|---|---|---|
|
|
969
|
+
| `stack` | `D1011` | Max depth of eval-apply cycle |
|
|
970
|
+
| `timeout` | `D1012` | Max milliseconds of execution |
|
|
971
|
+
| `sequence` | `D2015` | Max items in any sequence |
|
|
972
|
+
| `RegexEngine` | — | Custom regex constructor for ReDoS protection |
|
|
973
|
+
|
|
974
|
+
---
|
|
975
|
+
|
|
976
|
+
## Node-RED Integration
|
|
977
|
+
|
|
978
|
+
JSONata is the **default expression language** in Node-RED. Key usage points:
|
|
979
|
+
|
|
980
|
+
### Change Node — "Set" with JSONata Expression
|
|
981
|
+
|
|
982
|
+
The "change" node has a "JSONata" expression type. The expression receives the full `msg` object as its context (`$$` = `msg`).
|
|
983
|
+
|
|
984
|
+
```jsonata
|
|
985
|
+
-- Transform msg.payload
|
|
986
|
+
payload.temperature & "°C"
|
|
987
|
+
|
|
988
|
+
-- Access other msg properties
|
|
989
|
+
"Topic: " & topic & ", Value: " & payload
|
|
990
|
+
|
|
991
|
+
-- Create a new object from msg
|
|
992
|
+
{"sensor": topic, "value": payload, "timestamp": $now()}
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
### Switch Node — Condition Expressions
|
|
996
|
+
|
|
997
|
+
The "switch" node supports JSONata expressions for conditions. The expression must return `true`/`false`:
|
|
998
|
+
|
|
999
|
+
```jsonata
|
|
1000
|
+
payload.temperature > 30
|
|
1001
|
+
$contains(topic, "alarm")
|
|
1002
|
+
$exists(payload.error)
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
### Context Access in Node-RED
|
|
1006
|
+
|
|
1007
|
+
The `msg` object is the root `$$`. All msg properties are directly accessible by name:
|
|
1008
|
+
- `payload` → `msg.payload`
|
|
1009
|
+
- `topic` → `msg.topic`
|
|
1010
|
+
- `payload.sensor.value` → nested access within payload
|
|
1011
|
+
|
|
1012
|
+
### Common Node-RED JSONata Patterns
|
|
1013
|
+
|
|
1014
|
+
```jsonata
|
|
1015
|
+
-- Extract and rename fields
|
|
1016
|
+
{"device": topic, "reading": payload, "unit": "°C"}
|
|
1017
|
+
|
|
1018
|
+
-- Conditional transformation
|
|
1019
|
+
payload > 100 ? "HIGH" : payload > 50 ? "MEDIUM" : "LOW"
|
|
1020
|
+
|
|
1021
|
+
-- Filter array within payload
|
|
1022
|
+
payload[value > 10]
|
|
1023
|
+
|
|
1024
|
+
-- Aggregate array data
|
|
1025
|
+
$sum(payload.readings)
|
|
1026
|
+
$average(payload.*.temperature)
|
|
1027
|
+
|
|
1028
|
+
-- Timestamp enrichment
|
|
1029
|
+
{"data": payload, "receivedAt": $now()}
|
|
1030
|
+
|
|
1031
|
+
-- Default value pattern
|
|
1032
|
+
payload.value ?: 0
|
|
1033
|
+
|
|
1034
|
+
-- Check if property exists
|
|
1035
|
+
$exists(payload.error) ? "Error: " & payload.error : "OK"
|
|
1036
|
+
|
|
1037
|
+
-- Merge multiple properties
|
|
1038
|
+
$merge([payload.metadata, {"processed": true, "timestamp": $now()}])
|
|
1039
|
+
```
|