Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

mozjexl

mozilla7.2kMIT1.1.6

Javascript Expression Language: Powerful context-based expression parser and evaluator

JSON, expression, evaluator, parser, target, context, jsep, filter, selector

readme

mozJexl is a fork of Jexl for use at Mozilla, specifically as a part of SHIELD and Normandy.


Promises/A+ logo # Jexl [![Build Status](https://travis-ci.org/TechnologyAdvice/Jexl.svg?branch=master)](https://travis-ci.org/TechnologyAdvice/Jexl) [![Code Climate](https://codeclimate.com/github/TechnologyAdvice/Jexl/badges/gpa.svg)](https://codeclimate.com/github/TechnologyAdvice/Jexl) [![Test Coverage](https://codeclimate.com/github/TechnologyAdvice/Jexl/badges/coverage.svg)](https://codeclimate.com/github/TechnologyAdvice/Jexl) Javascript Expression Language: Powerful context-based expression parser and evaluator

Quick start

Use it with promises or callbacks:

var context = {
    name: {first: 'Sterling', last: 'Archer'},
    assoc: [
        {first: 'Lana', last: 'Kane'},
        {first: 'Cyril', last: 'Figgis'},
        {first: 'Pam', last: 'Poovey'}
    ],
    age: 36
};

// Filter an array
jexl.eval('assoc[.first == "Lana"].last', context).then(function(res) {
    console.log(res); // Output: Kane
});

// Do math
jexl.eval('age * (3 - 1)', context, function(err, res) {
    console.log(res); // Output: 72
});

// Concatenate
jexl.eval('name.first + " " + name["la" + "st"]', context).then(function(res) {
    console.log(res); // Output: Sterling Archer
});

// Compound
jexl.eval('assoc[.last == "Figgis"].first == "Cyril" && assoc[.last == "Poovey"].first == "Pam"', context)
    .then(function(res) {
        console.log(res); // Output: true
    });

// Use array indexes
jexl.eval('assoc[1]', context, function(err, res) {
    console.log(res.first + ' ' + res.last); // Output: Cyril Figgis
});

// Use conditional logic
jexl.eval('age > 62 ? "retired" : "working"', context).then(function(res) {
    console.log(res); // Output: working
});

// Transform
jexl.addTransform('upper', function(val) {
    return val.toUpperCase();
});
jexl.eval('"duchess"|upper + " " + name.last|upper', context).then(function(res) {
    console.log(res); // Output: DUCHESS ARCHER
});

// Transform asynchronously, with arguments
jexl.addTransform('getStat', function(val, stat) {
    return dbSelectByLastName(val, stat); // Returns a promise
});
jexl.eval('name.last|getStat("weight")', context, function(err, res) {
    if (err) console.log('Database Error', err.stack);
    else console.log(res); // Output: 184
});

// Add your own (a)synchronous operators
// Here's a case-insensitive string equality
jexl.addBinaryOp('_=', 20, function(left, right) {
    return left.toLowerCase() === right.toLowerCase();
});
jexl.eval('"Guest" _= "gUeSt"').then(function(val) {
    console.log(res); // Output: true
});

Installation

Jexl requires an environment that supports the Promise/A+ specification as standardized in ES6. Node.js version 0.12.0 and up is great right out of the box (no --harmony flag necessary), as well as the latest versions of many browsers. To support older browsers, just include a Promise library such as Bluebird.

For Node.js, type this in your project folder:

npm install jexl --save

For the frontend, drop dist/jexl.min.js into your project and include it on your page with:

<script src="path/to/jexl.min.js"></script>

Access Jexl the same way, backend or front:

var jexl = require('Jexl');

All the details

Unary Operators

Operation Symbol
Negate !

Binary Operators

Operation Symbol
Add, Concat +
Subtract -
Multiply *
Divide /
Divide and floor //
Modulus %
Power of ^
Logical AND &&
Logical OR ||

Comparisons

Comparison Symbol
Equal ==
Not equal !=
Greater than >
Greater than or equal >=
Less than <
Less than or equal <=
Element in array or string in

A note about in:

The in operator can be used to check for a substring: "Cad" in "Ron Cadillac", and it can be used to check for an array element: "coarse" in ['fine', 'medium', 'coarse']. However, the == operator is used behind-the-scenes to search arrays, so it should not be used with arrays of objects. The following expression returns false: {a: 'b'} in [{a: 'b'}].

Ternary operator

Conditional expressions check to see if the first segment evaluates to a truthy value. If so, the consequent segment is evaluated. Otherwise, the alternate is. If the consequent section is missing, the test result itself will be used instead.

Expression Result
"" ? "Full" : "Empty" Empty
"foo" in "foobar" ? "Yes" : "No" Yes
{agent: "Archer"}.agent ?: "Kane" Archer

Native Types

Type Examples
Booleans true, false
Strings "Hello "user"", 'Hey there!'
Numerics 6, -7.2, 5, -3.14159
Objects {hello: "world!"}
Arrays ['hello', 'world!']

Groups

Parentheses work just how you'd expect them to:

Expression Result
(83 + 1) / 2 42
1 < 3 && (4 > 2 || 2 > 4) true

Identifiers

Access variables in the context object by just typing their name. Objects can be traversed with dot notation, or by using brackets to traverse to a dynamic property name.

Example context:

{
    name: {
        first: "Malory",
        last: "Archer"
    },
    exes: [
        "Nikolai Jakov",
        "Len Trexler",
        "Burt Reynolds"
    ],
    lastEx: 2
}
Expression Result
name.first Malory
name['la' + 'st'] Archer
exes[2] Burt Reynolds
exes[lastEx - 1] Len Trexler

Collections

Collections, or arrays of objects, can be filtered by including a filter expression in brackets. Properties of each collection can be referenced by prefixing them with a leading dot. The result will be an array of the objects for which the filter expression resulted in a truthy value.

Example context:

{
    employees: [
        {first: 'Sterling', last: 'Archer', age: 36},
        {first: 'Malory', last: 'Archer', age: 75},
        {first: 'Lana', last: 'Kane', age: 33},
        {first: 'Cyril', last: 'Figgis', age: 45},
        {first: 'Cheryl', last: 'Tunt', age: 28}
    ],
    retireAge: 62
}
Expression Result
employees[.first == 'Sterling'] [{first: 'Sterling', last: 'Archer', age: 36}]
employees[.last == 'Tu' + 'nt'].first Cheryl
employees[.age >= 30 && .age < 40] [{first: 'Sterling', last: 'Archer', age: 36},{first: 'Lana', last: 'Kane', age: 33}]
employees[.age >= 30 && .age < 40][.age < 35] [{first: 'Lana', last: 'Kane', age: 33}]
employees[.age >= retireAge].first Malory

Transforms

The power of Jexl is in transforming data, synchronously or asynchronously. Transform functions take one or more arguments: The value to be transformed, followed by anything else passed to it in the expression. They must return either the transformed value, or a Promise that resolves with the transformed value. Add them with jexl.addTransform(name, function).

jexl.addTransform('split', function(val, char) {
    return val.split(char);
});
jexl.addTransform('lower', function(val) {
    return val.toLowerCase();
});
Expression Result
"Pam Poovey"|lower|split(' ')[1] poovey
"password==guest"|split('=' + '=') ['password', 'guest']

Advanced Transforms

Using Transforms, Jexl can support additional string formats like embedded JSON, YAML, XML, and more. The following, with the help of the xml2json module, allows XML to be traversed just as easily as plain javascript objects:

var xml2json = require('xml2json');

jexl.addTransform('xml', function(val) {
    return xml2json.toJson(val, {object: true});
});

var context = {
    xmlDoc:
        "<Employees>" +
            "<Employee>" +
                "<FirstName>Cheryl</FirstName>" +
                "<LastName>Tunt</LastName>" +
            "</Employee>" +
            "<Employee>" +
                "<FirstName>Cyril</FirstName>" +
                "<LastName>Figgis</LastName>" +
            "</Employee>" +
        "</Employees>"
};

var expr = 'xmlDoc|xml.Employees.Employee[.LastName == "Figgis"].FirstName';

jexl.eval(expr, context).then(function(res) {
    console.log(res); // Output: Cyril
});

Context

Variable contexts are straightforward Javascript objects that can be accessed in the expression, but they have a hidden feature: they can include a Promise object, and when that property is used, Jexl will wait for the Promise to resolve and use that value!

API

jexl.Jexl

A reference to the Jexl constructor. To maintain separate instances of Jexl with each maintaining its own set of transforms, simply re-instantiate with new jexl.Jexl().

jexl.addBinaryOp({string} operator, {number} precedence, {function} fn)

Adds a binary operator to the Jexl instance. A binary operator is one that considers the values on both its left and right, such as "+" or "==", in order to calculate a result. The precedence determines the operator's position in the order of operations (please refer to lib/grammar.js to see the precedence of existing operators). The provided function will be called with two arguments: a left value and a right value. It should return either the resulting value, or a Promise that resolves to the resulting value.

jexl.addUnaryOp({string} operator, {function} fn)

Adds a unary operator to the Jexl instance. A unary operator is one that considers only the value on its right, such as "!", in order to calculate a result. The provided function will be called with one argument: the value to the operator's right. It should return either the resulting value, or a Promise that resolves to the resulting value.

jexl.addTransform({string} name, {function} transform)

Adds a transform function to this Jexl instance. See the Transforms section above for information on the structure of a transform function.

jexl.addTransforms({{}} map)

Adds multiple transforms from a supplied map of transform name to transform function.

jexl.getTransform({string} name)

Returns {function|undefined}. Gets a previously set transform function, or undefined if no function of that name exists.

jexl.eval({string} expression, {{}} [context], {function} [callback])

Returns {Promise<*>}. Evaluates an expression. The context map and callback function are optional. If a callback is specified, it will be called with the standard signature of {Error} first argument, and the expression's result in the second argument. Note that if a callback function is supplied, the returned Promise will already have a .catch() attached to it.

jexl.removeOp({string} operator)

Removes a binary or unary operator from the Jexl instance. For example, "^" can be passed to eliminate the "power of" operator.

License

Jexl is licensed under the MIT license. Please see LICENSE.txt for full details.

Credits

Jexl was designed and created at TechnologyAdvice.

changelog

Jexl Change Log

This project adheres to Semantic Versioning.

[Development

Nothing yet!

v1.1.4

Fixed

  • Falsey identifiers are no longer treated as undefined

v1.1.3

Fixed

  • Binary operators after nested identifiers were not balanced properly, resulting in a broken expression/AST
  • Gulp (or one of its plugins) had a breaking change in a minor release, preventing the frontend build from running. This build method will be removed from the next major version of Jexl. For now, Jexl is now version- locked to the original gulp+plugins that worked.

v1.1.2

Changed

  • Code coverage thresholds are now enforced through gulp coverage-test

Fixed

  • Operators found in identifier names (such as 'in' in 'incident') were being tokenized separately from the rest of the identifier

v1.1.1

Fixed

  • Minus did not denote a negative number at the start of a ternary's consequent section

v1.1.0

Added

  • The ability to define new binary and unary operators, or override existing ones.
  • The ability to delete existing binary and unary operators.

v1.0.2 = 2015-03-08

Fixed

  • Bad Gulpfile resulted in frontend dist falling out of sync. Fixed and re-synced.

v1.0.1 - 2015-03-08

Changed

  • Refactored Parser and Evaluator. Both operations are now marginally faster.
  • Removed balance tracking in favor of passing maps of token types at which the sub-parser should stop.

Fixed

  • Object literals could not be defined in the consequent section of a ternary expression.

v1.0.0 - 2015-03-04

Added

  • Object literals. Objects can now be defined inline with {standard: 'syntax'}.
  • Array literals. Arrays can also be defined with ["standard", 'syntax'].
  • The 'in' operator, for checking to see if a string appears inside a larger string, or if an element exists in an array.
  • Ternary expressions with this ? "standard" : "syntax"
  • Ternary expressions with alternate ?: "syntax"

Changed

  • Simplified Grammar, reduced RAM footprint
  • Dot notation can now be used to access properties of literals, such as "someString".length or {foo: 'bar'}.foo.
  • Transform syntax has changed. Arguments are now passed in parentheses, and multiple arguments can be defined. Arguments are no longer limited to object literals.

v0.2.0 - 2015-03-02

Added

  • "Divide and floor" operator: //
  • Documentation outlining running expressions against XML.

v0.1.0 - 2015-03-02

Added

  • Initial release