Comparing version 1.0.0 to 2.0.0
{ | ||
"name": "json-e", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "json parameterization module inspired from json-parameterization", | ||
"main": "./dist/index.js", | ||
"scripts": { | ||
"build": "babel src --presets babel-preset-es2015 --out-dir dist -s", | ||
"test": "npm run build && mocha --ui tdd ./dist/tests.js", | ||
"prepublish": "npm run build && mocha --ui tdd ./dist/tests.js" | ||
"compile": "babel-compile -p taskcluster src:lib test:.test", | ||
"pretest": "npm run compile", | ||
"test": "npm run compile && mocha .test/*_test.js", | ||
"prepublish": "npm run compile && npm test", | ||
"build-demo": "cd demo && yarn && yarn build", | ||
"start-demo": "cd demo && yarn && yarn start" | ||
}, | ||
"files": [ | ||
"lib" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/hammad13060/json-e.git" | ||
"url": "https://github.com/taskcluster/json-e" | ||
}, | ||
"author": "Hammad Akhtar <hammad13060@iiitd.ac.in>", | ||
"license": "MIT", | ||
"author": "", | ||
"license": "MPL-2.0", | ||
"dependencies": { | ||
"assume": "^1.4.1", | ||
"lodash": "^4.17.2", | ||
"notevil": "^1.0.0" | ||
"es6-error": "^4.0.1" | ||
}, | ||
"devDependencies": { | ||
"babel-cli": "^6.18.0", | ||
"babel-preset-es2015": "^6.18.0", | ||
"mocha": "^3.2.0" | ||
"assume": "^1.4.1", | ||
"babel-cli": "^6.24.1", | ||
"babel-compile": "^2.0.0", | ||
"babel-preset-taskcluster": "^3.0.0", | ||
"browserify": "^14.4.0", | ||
"eslint-config-taskcluster": "^2.0.0", | ||
"eslint-plugin-taskcluster": "^1.0.2", | ||
"mocha": "^3.2.0", | ||
"mocha-eslint": "^3.0.1", | ||
"source-map-support": "^0.4.11", | ||
"timekeeper": "^1.0.0" | ||
}, | ||
"engines": { | ||
"node": "0.12.x", | ||
"npm": "2.7.x" | ||
"node": "^6.4.0" | ||
} | ||
} |
483
README.md
@@ -1,244 +0,389 @@ | ||
# json-e | ||
json-e is a javascript object prarmetrization module written for embedding context in a javascript object. It supports | ||
following features | ||
# [JSON-e](https://taskcluster.github.io/json-e) | ||
## inteface | ||
JSON-e is a data-structure parameterization system written for embedding | ||
context in JSON objects. | ||
This module exposes following interface | ||
The central idea is to treat a data structure as a "template" and transform it, | ||
using another data structure as context, to produce an output data structure. | ||
There are countless libraries to do this with strings, such as | ||
[mustache](https://mustache.github.io/). What makes JSON-e unique is that it | ||
operates on data structures, not on their textual representation. This allows | ||
input to be written in a number of formats (JSON, YAML, etc.) or even generated | ||
dynamically. It also means that the output cannot be "invalid", even when | ||
including large chunks of contextual data. | ||
JSON-e is also designed to be safe for use on untrusted data. It never uses | ||
`eval` or any other function that might result in arbitrary code execution. It | ||
also disallows unbounded iteration, so any JSON-e rendering operation will | ||
finish in finite time. | ||
# Interface | ||
The JS module exposes following interface: | ||
```javascript | ||
// template is js object to be rendered | ||
// context provides functions/properties to be used | ||
var par = Parameterize(template1, context1); //constructor | ||
par.setNewTemplate(template2); //replaces template1 with template2 | ||
par.setNewContext(context2); //replaces context1 with context2 | ||
par.getTemplate(); //returns current template | ||
par.getContext(); //returns current context | ||
par.render(); //renders template using context | ||
import jsone from 'json-e'; | ||
var template = {a: {$eval: "foo.bar"}}; | ||
var context = {foo: {bar: "zoo"}}; | ||
console.log(jsone(template, context)); | ||
// -> { a: 'zoo' } | ||
``` | ||
## substitution | ||
It follows this "{{ expression }}" syntax. expression can be anything javascript eval can | ||
digest and provide a context object. | ||
Note that the context can contain functions, and those functions can be called | ||
from the template: | ||
```javascript | ||
//substitution | ||
//propert access | ||
var template = {a: "{{ foo.bar }}"}; | ||
var template = {$eval: "foo(1)"}; | ||
var context = {"foo": function(x) { return x + 2; }}; | ||
console.log(jsone(template, context)); | ||
// -> 3 | ||
``` | ||
var context = {foo: { | ||
bar: "zoo" | ||
}}; | ||
# Language | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
The examples here are given in YAML for ease of reading. Of course, the | ||
rendering operation takes place on the parsed data, so the input format is | ||
irrelevant to its operation. | ||
/* | ||
output > {a: "zoo"}; | ||
*/ | ||
## Simple Operations | ||
All JSON-e directives involve the `$` character, so a template without any directives is | ||
rendered unchanged: | ||
```yaml | ||
context: {} | ||
template: {key: [1,2,{key2: 'val', key3: 1}, true], f: false} | ||
result: {key: [1,2,{key2: 'val', key3: 1}, true], f: false} | ||
``` | ||
```javascript | ||
//substitution | ||
//function evaluation | ||
var template = {a: "{{ foo.bar() }}"}; | ||
The simplest form of substitution occurs within strings, using `${..}`: | ||
var context = {foo: { | ||
bar: function() {return 1;} | ||
}}; | ||
```yaml | ||
context: {key: 'world', num: 1} | ||
template: {message: 'hello ${key}', 'k=${num}': true} | ||
result: {message: 'hello world', 'k=1': true} | ||
``` | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
The bit inside the `${..}` is an expression, and must evaluate to something | ||
that interpolates obviously into a string (so, a string, number, boolean, or | ||
null). The expression syntax is described in more detail below. | ||
/* | ||
output > {a: "1"}; | ||
*/ | ||
Values interpolate as their JSON literal values: | ||
```yaml | ||
context: {num: 3, t: true, f: false, nil: null} | ||
template: ["number: ${num}", "booleans: ${t} ${f}", "null: ${nil}"] | ||
result: ["number: 3", "booleans: true false", "null: null"] | ||
``` | ||
Note that object keys can be interpolated, too: | ||
## evaluation | ||
```yaml | ||
context: {name: 'foo', value: 'bar'} | ||
template: {"tc_${name}": "${value}"} | ||
result: {"tc_foo": "bar"} | ||
``` | ||
It follows this "${ expression }" or {$eval: expression} syntax. expression can be anything javascript eval can | ||
digest and provide a context object. | ||
The string `${` can be escaped as `$${`. | ||
```javascript | ||
var template = {a: "${ foo.bar }"}; | ||
## Operators | ||
var context = {foo: { | ||
bar: 123 | ||
}}; | ||
JSON-e defines a bunch of operators. Each is represented as an object with a | ||
property beginning with `$`. This object can be buried deeply within the | ||
template. Some operators take additional arguments as properties of the same | ||
object. | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
### `$eval` | ||
/* | ||
output > {a: 123}; | ||
*/ | ||
The `$eval` operator evaluates the given expression and is replaced with the | ||
result of that evaluation. Unlike with string interpolation, the result need | ||
not be a string, but can be an arbitrary data structure. | ||
```yaml | ||
context: | ||
settings: | ||
staging: | ||
transactionBackend: mock | ||
production: | ||
transactionBackend: customerdb | ||
template: {config: {$eval: 'settings.staging'}} | ||
result: {config: {transactionBackend: 'mock'}} | ||
``` | ||
```javascript | ||
var template = {a: "${ foo.bar() }"}; | ||
The expression syntax is described in more detail below. | ||
var context = {foo: { | ||
bar: function() {return 1;} | ||
}}; | ||
### `$json` | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
The `$json` operator formats the given value as JSON. It does not evaluate the | ||
value (use `$eval` for that). While this can be useful in some cases, it is an | ||
unusual case to include a JSON string in a larger data structure. | ||
/* | ||
output > {a: 1}; | ||
*/ | ||
```yaml | ||
context: {a: 1, b: 2} | ||
template: {$json: [a, b, {$eval: 'a+b'}, 4]} | ||
result: '["a", "b", 3, 4]' | ||
``` | ||
```javascript | ||
var template = {a: : {$eval: "{{ foo.bar() }}"}}; | ||
### Truthiness | ||
var context = {foo: { | ||
bar: function() {return 1;} | ||
}}; | ||
Many values can be evaluated in context where booleans are required, | ||
not just booleans themselves. JSON-e defines the following values as false. | ||
Anything else will be true. | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
```yaml | ||
context: {a: null, b: [], c: {}, d: "", e: 0, f: false} | ||
template: {$if: 'a || b || c || d || e || f', then: "uh oh", else: "falsy" } | ||
result: "falsy" | ||
``` | ||
/* | ||
output > {a: "1"}; | ||
*/ | ||
### `$if` - `then` - `else` | ||
The `$if` operator supports conditionals. It evaluates the given value, and | ||
replaces itself with the `then` or `else` properties. If either property is | ||
omitted, then the expression is omitted from the parent object. | ||
```yaml | ||
context: {cond: true} | ||
template: {key: {$if: 'cond', then: 1}, k2: 3} | ||
result: {key: 1, k2: 3} | ||
``` | ||
```javascript | ||
var template = {a: {$eval: "${ foo.bar() }"}}; | ||
```yaml | ||
context: {x: 10} | ||
template: {$if: 'x > 5', then: 1, else: -1} | ||
result: 1 | ||
``` | ||
var context = {foo: { | ||
bar: function() {return 1;} | ||
}}; | ||
```yaml | ||
context: {cond: false} | ||
template: [1, {$if: 'cond', else: 2}, 3] | ||
result: [1,2,3] | ||
``` | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
```yaml | ||
context: {cond: false} | ||
template: {key: {$if: 'cond', then: 2}, other: 3} | ||
result: {other: 3} | ||
``` | ||
/* | ||
output > {a: 1}; | ||
*/ | ||
### `$flatten` | ||
The `$flatten` operator flattens an array of arrays into one array. | ||
```yaml | ||
context: {} | ||
template: {$flatten: [[1, 2], [3, 4], [5]]} | ||
result: [1, 2, 3, 4, 5] | ||
``` | ||
### `$flattenDeep` | ||
The `$flattenDeep` operator deeply flattens an array of arrays into one array. | ||
## array access | ||
```yaml | ||
context: {} | ||
template: {$flattenDeep: [[1, [2, [3]]]]} | ||
result: [1, 2, 3] | ||
``` | ||
We can access array elements using $array_name(index) syntax | ||
### `$fromNow` | ||
```javascript | ||
var template = {image_version: "{{task.$images(0).$versions(0)}}", name: "{{task.$images(0).name}}"}; | ||
var context = {task: { | ||
images: [{versions: ["12.10", ], name: "ubuntu"}] | ||
}}; | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
The `$fromNow` operator is a shorthand for the built-in function `fromNow`. It | ||
creates a JSON (ISO 8601) datestamp for a time relative to the current time. | ||
The offset is specified by a sequence of number/unit pairs in a string. For | ||
example: | ||
/* | ||
output > {image_version: "12.10", name: "ubuntu"} | ||
*/ | ||
```yaml | ||
context: {} | ||
template: {$fromNow: '2 days 1 hour'} | ||
result: '2017-01-19T16:27:20.974Z' | ||
``` | ||
The available units are `day`, `hour`, and `minute`, for all of which a plural | ||
is also accepted. | ||
### `$let` | ||
## $if construct | ||
The `$let` operator evaluates an expression using a context amended with the | ||
given values. It is analogous to the Haskell `where` clause. | ||
We can use $if contruct to choose between two different objects for evaluation | ||
```yaml | ||
context: {} | ||
template: {$let: {ts: 100, foo: 200}, | ||
in: [{$eval: "ts+foo"}, {$eval: "ts-foo"}, {$eval: "ts*foo"}]} | ||
result: [300, -100, 20000] | ||
``` | ||
The `$let` operator here added the `ts` and `foo` variables to the scope of | ||
the context and accordingly evaluated the `in` clause using those variables | ||
to return the correct result. | ||
```javascript | ||
var template = {a: { | ||
$if: "${ key1 < key2 }", | ||
$then: "${ foo.func1() }", | ||
$else: "${ foo.func2() }" | ||
}}; | ||
### `$map` | ||
var context = {foo: { | ||
func1: function() {return 1;}, | ||
func2: function() {return 2;} | ||
}, key1: 1, key2: 2}; | ||
The `$map` operator evaluates an expression for each value of the given array, | ||
constructing the result as an array of the evaluated values. | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
```yaml | ||
context: {a: 1} | ||
template: | ||
$map: [2, 4, 6] | ||
each(x): {$eval: 'x + a'} | ||
result: [3, 5, 7] | ||
``` | ||
/* | ||
output > {a: 1}; | ||
*/ | ||
The array is the value of the `$map` property, and the expression to evaluate | ||
is given by `each(var)` where `var` is the name of the variable containing each | ||
element. | ||
### `$merge` | ||
The `$merge` operator merges an array of objects, returning a single object | ||
that combines all of the objects in the array, where the right-side objects | ||
overwrite the values of the left-side ones. | ||
```yaml | ||
context: {} | ||
template: {$merge: [{a: 1, b: 1}, {b: 2, c: 3}, {d: 4}]} | ||
result: {a: 1, b: 2, c: 3, d: 4} | ||
``` | ||
$then and $else also supports substitution and recursive object evaluation. Following is an example for recursive object evaluation | ||
### `$sort` | ||
```javascript | ||
var template = {a: { | ||
$if: "${ key1 > key2 }", | ||
$then: {b: "${ foo.func1() }"}, | ||
$else: {b: "${ foo.func2() }"} | ||
}}; | ||
The `$sort` operator sorts the given array. It takes a `by(var)` property which | ||
should evaluate to a comparable value for each element. The `by(var)` property | ||
defaults to the identity function. | ||
var context = {foo: { | ||
func1: function() {return 1;}, | ||
func2: function() {return 2;} | ||
}, key1: 1, key2: 2}; | ||
```yaml | ||
context: {} | ||
template: | ||
$sort: [{a: 2}, {a: 1, b: []}, {a: 3}] | ||
by(x): 'x.a' | ||
result: [{a: 1, b: []}, {a: 2}, {a: 3}] | ||
``` | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
### `$reverse` | ||
/* | ||
output > {a: {b: 1}}; | ||
*/ | ||
The `$reverse` operator simply reverses the given array. | ||
```yaml | ||
context: {} | ||
template: {$reverse: [3, 4, 1, 2]} | ||
result: [2, 1, 4, 3] | ||
``` | ||
## $switch construct | ||
this construct can be used in the following ways | ||
## Escaping operators | ||
```javascript | ||
var template = {a: { | ||
"$switch": "{{ 'case' + a }}", | ||
case1: "foo", | ||
case2: "bar" | ||
}}; | ||
You can use `$$` to escape json-e operators. For example: | ||
var context = {a: "1"}; | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
/* | ||
output > {a: "foo"}; | ||
*/ | ||
```yaml | ||
context: {} | ||
template: {$$reverse: [3, 2, {$$eval: '2 - 1'}, 0]} | ||
result: {$reverse: [3, 2, {$eval: '2 - 1'}, 0]} | ||
``` | ||
```javascript | ||
var template = {a: { | ||
"$switch": "{{ 'case' + a }}", | ||
caseA: {b:1} | ||
}}; | ||
## Expressions | ||
var context = {a: "A"}; | ||
var par = new Parameterize(template, context); | ||
par.render(); | ||
console.log(par.getTemplate()); | ||
Expression are given in a simple Python- or JavaScript-like language. It | ||
supports the following: | ||
/* | ||
output > {a: {b: 1}}; | ||
*/ | ||
* Numeric literals (decimal only) | ||
* String literals (enclosed in `'` or `"`, with no escaping) | ||
* Arrays in JSON format (`[.., ..]`) | ||
* Objects in JS format: `{"foo": 10}` or `{foo: 10}` | ||
* Parentheses for grouping (`(a + b) * c`) | ||
* Arithmetic on integers (`+`, `-`, `*`, `/`, `**` for exponentiation), with unary `-` and `+` | ||
* String concatenation (`+`) | ||
* Comparison of strings to strings or numbers to numbers (`<`, `<=`, `>`, `>=`) | ||
* Equality of anything (`==`, `!=`) | ||
* Boolean operators (`||`, `&&`, `!`) | ||
* Identifiers referring to variables (matching `/[a-zA-Z_][a-zA-Z_0-9]*/`) | ||
* Object property access: `obj.prop` or `obj["prop"]` | ||
* `obj,prop` is an error if there is no such property; in the same case `obj["prop"]` evaluates to `null`. | ||
* Array and string indexing and slicing with Python semantics | ||
* `array[1]` -- second element of array (zero-indexed) | ||
* `array[1:4]` -- second through fourth elements of the array (the slice includes the left index and excludes the right index) | ||
* `array[1:]` -- second through last element of the array | ||
* `array[:3]` -- first through third element of the array | ||
* `array[-2:]` -- the last two elements of the array | ||
* `array[:-1]` -- all but the last element of the array | ||
* `string[3]` -- fourth character of the string | ||
* `string[-4:]` -- all but the last four characters of the string | ||
* Containment operator: | ||
* `"string" in object` -- true if the object has the given property | ||
* `"string" in array` -- true if the string is an array element | ||
* `number in array` -- true if the number is an array element | ||
* `"string" in "another string"` -- true if the first string is a substring of the second | ||
* Function invocation: `fn(arg1, arg2)` | ||
### Built-In Functions | ||
The expression language provides a laundry-list of built-in functions. Library | ||
users can easily add additional functions, or override the built-ins, as part | ||
of the context. | ||
* `fromNow(x)` -- JSON datestamp for a time relative to the current time | ||
* `min(a, b, ..)` -- the smallest of the arguments | ||
* `max(a, b, ..)` -- the largest of the arguments | ||
* `sqrt(x)`, `ceil(x)`, `floor(x)`, `abs(x)` -- mathematical functions | ||
* `lowercase(s)`, `uppercase(s)` -- convert string case | ||
* `str(x)` -- convert string, number, boolean, or array to string | ||
* `len(x)` -- length of a string or array | ||
### Custom Functions | ||
The context supplied to JSON-e can contain JS function objects. These will be | ||
available just like the built-in functions are. For example: | ||
```js | ||
var context = { | ||
imageData: function(img) { | ||
return ...; | ||
}, | ||
}; | ||
var template = { | ||
title: "Trip to Hawaii", | ||
thumbnail: {$eval: 'imageData("hawaii")'}, | ||
}; | ||
return jsone(template, context); | ||
``` | ||
instead of js object we can use ${ expression } syntax as case value. | ||
NOTE: Context functions are called synchronously. Any complex asynchronous | ||
operations should be handled before rendering the template. | ||
## Note | ||
NOTE: If the template is untrusted, it can pass arbitrary data to functions | ||
in the context, which must guard against such behavior. For example, if the | ||
`imageData` function above reads data from a file, it must sanitize the | ||
filename before opening it. | ||
This module provides recursive evaluation of js objects and arrays for templates, | ||
along with deep property access from the context. | ||
# Development and testing | ||
For more info checkout src/tests.js | ||
You should run `npm install` to install the required packages for json-e's | ||
execution and development. | ||
You can run `./test.sh` to run json-e's unit tests and the `bundle.js` check. | ||
This is a breakdown of the commands inside the `test.sh` file. | ||
```bash | ||
# Run JavaScript unit tests | ||
npm test | ||
# Run Python unit tests | ||
python setup.py test | ||
# bundle.js check. This section makes sure that | ||
# the demo website's bundle.js file is updated. | ||
mv docs/bundle.js docs/bundle.diff.js | ||
npm run-script build-demo | ||
diff docs/bundle.js docs/bundle.diff.js | ||
``` | ||
You can also run the following command to | ||
update the demo website bundle.js file. | ||
```bash | ||
npm run-script build-demo | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Copyleft License
License(Experimental) Copyleft license information was found.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
144503
1
19
1284
390
11
4
70
2
2
+ Addedes6-error@^4.0.1
+ Addedes6-error@4.1.1(transitive)
- Removedassume@^1.4.1
- Removedlodash@^4.17.2
- Removednotevil@^1.0.0
- Removedassume@1.5.2(transitive)
- Removeddeep-eql@0.1.3(transitive)
- Removedesprima@1.0.4(transitive)
- Removedfn.name@1.0.1(transitive)
- Removedhoister@0.0.2(transitive)
- Removedlodash@4.17.21(transitive)
- Removednotevil@1.3.3(transitive)
- Removedobject-inspect@1.0.2(transitive)
- Removedpathval@0.1.1(transitive)
- Removedpruddy-error@1.0.2(transitive)
- Removedtype-detect@0.1.1(transitive)