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