1* [Full documentation](https://taskcluster.github.io/json-e)
2
3# JSON-e
4
5JSON-e is a data-structure parameterization system for embedding context in
6JSON objects.
7
8The central idea is to treat a data structure as a "template" and transform it,
9using another data structure as context, to produce an output data structure.
10
11There are countless libraries to do this with strings, such as
12[mustache](https://mustache.github.io/). What makes JSON-e unique is that it
13operates on data structures, not on their textual representation. This allows
14input to be written in a number of formats (JSON, YAML, etc.) or even generated
15dynamically. It also means that the output cannot be "invalid", even when
16including large chunks of contextual data.
17
18JSON-e is also designed to be safe for use on untrusted data. It never uses
19`eval` or any other function that might result in arbitrary code execution. It
20also disallows unbounded iteration, so any JSON-e rendering operation will
21finish in finite time.
22
23## Changes
24
25See
26[CHANGELOG.rst](https://github.com/taskcluster/json-e/blob/master/CHANGELOG.rst)
27for the changes in each version of this library.
28
29# Interface
30
31## JavaScript
32
33The JS module is installed with either of
34
35```shell
36npm install --save json-e
37yarn add json-e
38```
39
40The module exposes following interface:
41
42```javascript
43import jsone from 'json-e';
44
45var template = {a: {$eval: "foo.bar"}};
46var context = {foo: {bar: "zoo"}};
47console.log(jsone(template, context));
48// -> { a: 'zoo' }
49```
50
51Note that the context can contain functions, and those functions can be called
52from the template:
53
54```javascript
55var template = {$eval: "foo(1)"};
56var context = {"foo": function(x) { return x + 2; }};
57console.log(jsone(template, context));  // -> 3
58```
59
60*NOTE*: Context functions are called synchronously. Any complex asynchronous
61operations should be handled before rendering the template.
62
63*NOTE*: If the template is untrusted, it can pass arbitrary data to functions
64in the context, which must guard against such behavior.
65
66### Browser
67
68JSON-e is distributed as a CommonJS package is not designed to be included
69directly in a browser with `<script>`. Instead, it must be incorproated using a
70tool that understands CommonJS such as Webpack.  See
71[Neutrino](https://neutrino.js.org/) for an easy, configuration-free way to
72build such applications.
73
74## Python
75
76The Python distribution exposes a `render` function:
77
78```python
79import jsone
80
81template = {"a": {"$eval": "foo.bar"}}
82context = {"foo": {"bar": "zoo"}}
83print(jsone.render(template, context))  # -> {"a": "zoo"}
84```
85
86and also allows custom functions in the context:
87
88```python
89template = {"$eval": "foo(1)"}
90context = {"foo": lambda x: x + 2}
91print(jsone.render(template, context))  # -> 3
92```
93
94## Go (golang)
95
96The [golang package for json-e](https://godoc.org/github.com/taskcluster/json-e) exposes a `Render` function:
97
98```golang
99import (
100  "fmt"
101  "github.com/taskcluster/json-e"
102)
103
104// Template must be given using types:
105//   map[string]interface{}, []interface{}, float64, string, bool, nil
106// The same types that json.Unmarshal() will create when targeting an interface{}
107template := map[string]interface{}{
108  "result": map[string]interface{}{
109    "$eval": "f() + 5",
110  },
111}
112// Context can be JSON types just like template, but may also contain functions
113// these can JSON types as arguments, and return a value and optionally an error.
114context := map[string]interface{}{
115  "f": func() int { return 37 },
116}
117
118func main() {
119  value, err := jsone.Render(template, context)
120  fmt.Printf("%#v\n", value)
121}
122```
123
124## CLI
125
126You can use the 3rd party package [rjsone](https://wryun.github.io/rjsone/) to template
127JSON-e from the command line, passing templates/contexts as files or arguments and using
128stdout for the result.
129
130
131# Language Reference
132
133The examples here are given in YAML for ease of reading.  Of course, the
134rendering operation takes place on the parsed data, so the input format is
135irrelevant to its operation.
136
137## Simple Operations
138
139All JSON-e directives involve the `$` character, so a template without any directives is
140rendered unchanged:
141
142```yaml
143template: {key: [1,2,{key2: 'val', key3: 1}, true], f: false}
144context:  {}
145result:   {key: [1,2,{key2: 'val', key3: 1}, true], f: false}
146```
147
148## String Interpolation
149
150The simplest form of substitution occurs within strings, using `${..}`:
151
152```yaml
153template: {message: 'hello ${key}', 'k=${num}': true}
154context:  {key: 'world', num: 1}
155result:   {message: 'hello world', 'k=1': true}
156```
157
158The bit inside the `${..}` is an expression, and must evaluate to something
159that interpolates obviously into a string (so, a string, number, boolean,).
160If it is null, then the expression interpolates into an empty string.
161The expression syntax is described in more detail below.
162
163Values interpolate as their JSON literal values:
164
165```yaml
166template: ["number: ${num}", "booleans: ${t} ${f}", "null: ${nil}"]
167context: {num: 3, t: true, f: false, nil: null}
168result: ["number: 3", "booleans: true false", "null: "]
169```
170
171Note that object keys can be interpolated, too:
172
173```yaml
174template: {"tc_${name}": "${value}"}
175context: {name: 'foo', value: 'bar'}
176result: {"tc_foo": "bar"}
177```
178
179The string `${` can be escaped as `$${`.
180
181## Operators
182
183JSON-e defines a bunch of operators. Each is represented as an object with a
184property beginning with `$`. This object can be buried deeply within the
185template. Some operators take additional arguments as properties of the same
186object.
187
188### `$eval`
189
190The `$eval` operator evaluates the given expression and is replaced with the
191result of that evaluation. Unlike with string interpolation, the result need
192not be a string, but can be an arbitrary data structure.
193
194```yaml
195template: {config: {$eval: 'settings.staging'}}
196context:
197  settings:
198    staging:
199      transactionBackend: mock
200    production:
201      transactionBackend: customerdb
202result:   {config: {transactionBackend: 'mock'}}
203```
204
205The expression syntax is described in more detail below.
206
207Note that `$eval`'s value must be a string. "Metaprogramming" by providing a
208calculated value to eval is not allowed.  For example, `{$eval: {$eval:
209"${var1} + ${var2}"}}` is not valid JSON-e.
210
211### `$json`
212
213The `$json` operator formats the given value as JSON with sorted keys. It does
214not evaluate the value (use `$eval` for that). While this can be useful in some
215cases, it is an unusual case to include a JSON string in a larger data
216structure.
217
218```yaml
219template: {$json: [a, b, {$eval: 'a+b'}, 4]}
220context:  {a: 1, b: 2}
221result:   '["a", "b", 3, 4]'
222```
223
224### `$if` - `then` - `else`
225
226The `$if` operator supports conditionals. It evaluates the given value, and
227replaces itself with the `then` or `else` properties. If either property is
228omitted, then the expression is omitted from the parent object.
229
230```yaml
231template: {key: {$if: 'cond', then: 1}, k2: 3}
232context:  {cond: true}
233result:   {key: 1, k2: 3}
234```
235
236```yaml
237template: {$if: 'x > 5', then: 1, else: -1}
238context:  {x: 10}
239result:   1
240```
241
242```yaml
243template: [1, {$if: 'cond', else: 2}, 3]
244context: {cond: false}
245result: [1,2,3]
246```
247
248```yaml
249template: {key: {$if: 'cond', then: 2}, other: 3}
250context: {cond: false}
251result: {other: 3}
252```
253
254### `$flatten`
255
256The `$flatten` operator flattens an array of arrays into one array.
257
258```yaml
259template: {$flatten: [[1, 2], [3, 4], [5]]}
260context:  {}
261result:   [1, 2, 3, 4, 5]
262```
263
264### `$flattenDeep`
265
266The `$flattenDeep` operator deeply flattens an array of arrays into one array.
267
268```yaml
269template: {$flattenDeep: [[1, [2, [3]]]]}
270context:  {}
271result:   [1, 2, 3]
272```
273
274### `$fromNow`
275
276The `$fromNow` operator is a shorthand for the built-in function `fromNow`. It
277creates a JSON (ISO 8601) datestamp for a time relative to the current time
278(see the `now` builtin, below) or, if `from` is given, relative to that time.
279The offset is specified by a sequence of number/unit pairs in a string. For
280example:
281
282```yaml
283template: {$fromNow: '2 days 1 hour'}
284context:  {}
285result:   '2017-01-19T16:27:20.974Z'
286```
287
288```yaml
289template: {$fromNow: '1 hour', from: '2017-01-19T16:27:20.974Z'}
290context:  {}
291result:   '2017-01-19T17:27:20.974Z'
292```
293
294The available units are `day`, `hour`, and `minute`, for all of which a plural
295is also accepted.
296
297### `$let`
298
299The `$let` operator evaluates an expression using a context amended with the
300given values. It is analogous to the Haskell `where` clause.
301
302```yaml
303template: {$let: {ts: 100, foo: 200},
304           in: [{$eval: "ts+foo"}, {$eval: "ts-foo"}, {$eval: "ts*foo"}]}
305context: {}
306result: [300, -100, 20000]
307```
308
309The `$let` operator here added the `ts` and `foo` variables to the scope of
310the context and accordingly evaluated the `in` clause using those variables
311to return the correct result.
312
313The variable names in the `$let` value must be valid context variable names and
314must be written literally. That is, an expression like `{$let: {$eval:
315"extraVariables"}, in : ..}` is not allowed.
316
317### `$map`
318
319The `$map` operator evaluates an expression for each value of the given array or object,
320constructing the result as an array or object of the evaluated values.
321
322When given an array, map always returns an array.
323
324```yaml
325template:
326  $map: [2, 4, 6]
327  each(x): {$eval: 'x + a'}
328context:  {a: 1}
329result:   [3, 5, 7]
330```
331The array or object is the value of the `$map` property, and the expression to evaluate
332is given by `each(var)` where `var` is the name of the variable containing each
333element. In the case of iterating over an object, `var` will be an object with two keys:
334`key` and `val`. These keys correspond to a key in the object and its corresponding value.
335
336When $map is given an object, the expression defined by `each(var)` must evaluate to an
337object for each key/value pair (`key` and `val`).The objects constructed by each 'each(var)'
338can then be merged internally to give the resulting object with later keys overwriting
339the previous ones.Otherwise the expression becomes invalid for the $map operator
340
341```yaml
342template:
343  $map: {a: 1, b: 2, c: 3}
344  each(y): {'${y.key}x': {$eval: 'y.val + 1'}}
345context:  {}
346result: {ax: 2, bx: 3, cx: 4}
347```
348
349### `$match`
350
351The `$match` operator is not dissimilar to pattern matching operators. It gets an object, in which every key is a string expression(s) to evaluate to `true` or `false` based on the context. The result will be an array of things (all types are supported) that were values corresponding to the keys that were evaluated to `true`. The order of the things in the array will be arbitrary. If there are no matches, the result is an empty array.
352
353```yaml
354template: {$match: {"x == 10": "ten", "x == 20": "twenty"}}
355context: {x: 10}
356result: ["ten"]
357```
358
359```yaml
360template: {$match: {"x == 10 || x == 20": "tens", "x == 10": "ten"}}
361context: {x: 10}
362one possible result: ["tens", "ten"]
363another possible result: ["ten", "tens"]
364```
365```yaml
366template: {$match: {"x < 10": "tens"}}
367context: {x: 10}
368result: []
369```
370
371### `$merge`
372
373The `$merge` operator merges an array of objects, returning a single object
374that combines all of the objects in the array, where the right-side objects
375overwrite the values of the left-side ones.
376
377```yaml
378template: {$merge: [{a: 1, b: 1}, {b: 2, c: 3}, {d: 4}]}
379context:  {}
380result:   {a: 1, b: 2, c: 3, d: 4}
381```
382
383### `$mergeDeep`
384
385The `$mergeDeep` operator is like `$merge`, but it recurses into objects to
386combine their contents property by property.  Arrays are concatenated.
387
388```yaml
389template:
390  $mergeDeep:
391    - task:
392        payload:
393          command: [a, b]
394    - task:
395        extra:
396          foo: bar
397    - task:
398        payload:
399          command: [c]
400context:  {}
401result:
402  task:
403    extra:
404      foo: bar
405    payload:
406      command: [a, b, c]
407```
408
409### `$sort`
410
411The `$sort` operator sorts the given array. It takes a `by(var)` property which
412should evaluate to a comparable value for each element. The `by(var)` property
413defaults to the identity function.
414
415```yaml
416template:
417  $sort: [{a: 2}, {a: 1, b: []}, {a: 3}]
418  by(x): 'x.a'
419context:  {}
420result:   [{a: 1, b: []}, {a: 2}, {a: 3}]
421```
422
423### `$reverse`
424
425The `$reverse` operator simply reverses the given array.
426
427```yaml
428template: {$reverse: [3, 4, 1, 2]}
429context:  {}
430result:   [2, 1, 4, 3]
431```
432
433### Escaping operators
434
435All property names starting with `$` are reserved for JSON-e.
436You can use `$$` to escape such properties:
437
438```yaml
439template: {$$reverse: [3, 2, {$$eval: '2 - 1'}, 0]}
440context:  {}
441result:   {$reverse: [3, 2, {$eval: '2 - 1'}, 0]}
442```
443
444## Truthiness
445
446Many values can be evaluated in context where booleans are required,
447not just booleans themselves. JSON-e defines the following values as false.
448Anything else will be true.
449
450```yaml
451template: {$if: 'a || b || c || d || e || f', then: "uh oh", else: "falsy" }
452context: {a: null, b: [], c: {}, d: "", e: 0, f: false}
453result: "falsy"
454```
455
456## Expression Syntax
457
458Expression are given in a simple Python- or JavaScript-like expression
459language.  Its data types are limited to JSON types plus function objects.
460
461### Literals
462
463Literals are similar to those for JSON.  Numeric literals only accept integer
464and decimal notation. Strings do not support any kind of escaping. The use of
465`\n` and `\t` in the example below depends on the YAML parser to expand the
466escapes.
467
468```yaml
469template:
470  - {$eval: "1.3"}
471  - {$eval: "'abc'"}
472  - {$eval: '"abc"'}
473  - {$eval: "'\n\t'"}
474context: {}
475result:
476  - 1.3
477  - "abc"
478  - "abc"
479  - "\n\t"
480```
481
482Array and object literals also look much like JSON, with bare identifiers
483allowed as keys like in Javascript:
484
485```yaml
486template:
487  - {$eval: '[1, 2, "three"]'}
488  - {$eval: '{foo: 1, "bar": 2}'}
489context: {}
490result:
491  - [1, 2, "three"]
492  - {"foo": 1, "bar": 2}
493```
494
495### Context References
496
497Bare identifiers refer to items from the context or to built-ins (described below).
498
499```yaml
500template: {$eval: '[x, z, x+z]'}
501context: {x: 'quick', z: 'sort'}
502reslut: ['quick', 'sort', 'quicksort']
503```
504
505### Arithmetic Operations
506
507The usual arithmetic operators are all defined, with typical associativity and
508precedence:
509
510```yaml
511template:
512  - {$eval: 'x + z'}
513  - {$eval: 's + t'}
514  - {$eval: 'z - x'}
515  - {$eval: 'x * z'}
516  - {$eval: 'z / x'}
517  - {$eval: 'z ** 2'}
518  - {$eval: '(z / x) ** 2'}
519context: {x: 10, z: 20, s: "face", t: "plant"}
520result:
521  - 30
522  - "faceplant"
523  - 10
524  - 200
525  - 2
526  - 400
527  - 4
528```
529
530Note that strings can be concatenated with `+`, but none of the other operators
531apply.
532
533### Comparison Operations
534
535Comparisons work as expected.  Equality is "deep" in the sense of doing
536comparisons of the contents of data structures.
537
538```yaml
539template:
540  - {$eval: 'x < z'}
541  - {$eval: 'x <= z'}
542  - {$eval: 'x > z'}
543  - {$eval: 'x >= z'}
544  - {$eval: 'deep == [1, [3, {a: 5}]]'}
545  - {$eval: 'deep != [1, [3, {a: 5}]]'}
546context: {x: -10, z: 10, deep: [1, [3, {a: 5}]]}
547result: [true, true, false, false, true, false]
548```
549
550### Boolean Operations
551
552Boolean operations use C- and Javascript-style symbls `||`, `&&`, and `!`:
553
554```yaml
555template: {$eval: '!(false || false) && true'}
556context: {}
557result: true
558```
559
560### Object Property Access
561
562Like Javascript, object properties can be accessed either with array-index
563syntax or with dot syntax. Unlike Javascript, `obj.prop` is an error if `obj`
564does not have `prop`, while `obj['prop']` will evaulate to `null`.
565
566```yaml
567template: {$eval: 'v.a + v["b"]'}
568context: {v: {a: 'apple', b: 'bananna', c: 'carrot'}}
569result: 'applebananna'
570````
571
572### Indexing and Slicing
573
574Strings and arrays can be indexed and sliced using a Python-like indexing
575scheme.  Negative indexes are counted from the end of the value.  Slices are
576treated as "half-open", meaning that the result contains the first index and
577does not contain the second index.  A "backward" slice with the start index
578greater than the end index is treated as empty.
579
580```yaml
581template:
582  - {$eval: '[array[1], string[1]]'}
583  - {$eval: '[array[1:4], string[1:4]]'}
584  - {$eval: '[array[2:], string[2:]]'}
585  - {$eval: '[array[:2], string[:2]]'}
586  - {$eval: '[array[4:2], string[4:2]]'}
587  - {$eval: '[array[-2], string[-2]]'}
588  - {$eval: '[array[-2:], string[-2:]]'}
589  - {$eval: '[array[:-3], string[:-3]]'}
590context: {array: ['a', 'b', 'c', 'd', 'e'], string: 'abcde'}
591result:
592  - ['b', 'b']
593  - [['b', 'c', 'd'], 'bcd']
594  - [['c', 'd', 'e'], 'cde']
595  - [['a', 'b'], 'ab']
596  - [[], '']
597  - ['d', 'd']
598  - [['d', 'e'], 'de']
599  - [['a', 'b'], 'ab']
600```
601
602### Containment Operation
603
604The `in` keyword can be used to check for containment: a property in an object,
605an element in an array, or a substring in a string.
606
607```yaml
608template:
609  - {$eval: '"foo" in {foo: 1, bar: 2}'}
610  - {$eval: '"foo" in ["foo", "bar"]'}
611  - {$eval: '"foo" in "foobar"'}
612context: {}
613result: [true, true, true]
614```
615
616### Function Invocation
617
618Function calls are made with the usual `fn(arg1, arg2)` syntax. Functions are
619not JSON data, so they cannot be created in JSON-e, but they can be provided as
620built-ins or supplied in the context and called from JSON-e.
621
622### Built-In Functions and Variables
623
624The expression language provides a laundry-list of built-in functions/variables. Library
625users can easily add additional functions/variables, or override the built-ins, as part
626of the context.
627
628#### Time
629
630The built-in context value `now` is set to the current time at the start of
631evaluation of the template, and used as the default "from" value for `$fromNow`
632and the built-in `fromNow()`.
633
634```yaml
635template:
636  - {$eval: 'now'}
637  - {$eval: 'fromNow("1 minute")'}
638  - {$eval: 'fromNow("1 minute", "2017-01-19T16:27:20.974Z")'}
639context: {}
640result:
641  - '2017-01-19T16:27:20.974Z',
642  - '2017-01-19T16:28:20.974Z',
643  - '2017-01-19T16:28:20.974Z',
644```
645
646#### Math
647
648```yaml
649template:
650  # the smallest of the arguments
651  - {$eval: 'min(1, 3, 5)'}
652  # the largest of the arguments
653  - {$eval: 'max(2, 4, 6)'}
654  # mathematical functions
655  - {$eval: 'sqrt(16)'}
656  - {$eval: 'ceil(0.3)'}
657  - {$eval: 'floor(0.3)'}
658  - {$eval: 'abs(-0.3)'}
659context: {}
660result:
661  - 1
662  - 6
663  - 4
664  - 1
665  - 0
666  - 0.3
667```
668
669#### Strings
670
671```yaml
672template:
673  # convert string case
674  - {$eval: 'lowercase("Fools!")'}
675  - {$eval: 'uppercase("Fools!")'}
676  # convert string, number, boolean, or array to string
677  - {$eval: 'str(130)'}
678  # strip whitespace from left, right, or both ends of a string
679  - {$eval: 'lstrip("  room  ")'}
680  - {$eval: 'rstrip("  room  ")'}
681  - {$eval: 'strip("  room  ")'}
682context: {}
683result:
684  - "fools!"
685  - "FOOLS!"
686  - "130"
687  - "room  "
688  - "  room"
689  - room
690```
691
692#### Type
693
694The `typeof()` built-in returns the type of an object. Its behavior around
695`null` is reminiscent of JavaScript.
696
697```yaml
698template:
699 - "${typeof('abc')}"
700 - "${typeof(42)}"
701 - "${typeof(42.0)}"
702 - "${typeof(true)}"
703 - "${typeof([])}"
704 - "${typeof({})}"
705 - "${typeof(typeof)}"
706 - {$eval: "typeof(null)"}
707 - "${typeof(null)}"
708context: {}
709result:
710 - string
711 - number
712 - number
713 - boolean
714 - array
715 - object
716 - function
717 - null  # note: the value null, not the string "null"
718 - ''    # .. which interpolates to an empty string
719```
720
721#### Length
722
723The `len()` built-in returns the length of a string or array.
724
725```yaml
726template: {$eval: 'len([1, 2, 3])'}
727context: {}
728result: 3
729```
730
731