Javascript Expression Language: Powerful context-based expression parser and evaluator Mozjexl is a fork of Jexl for use at Mozilla, specifically as a part of SHIELD and Normandy.
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
mozjexl.eval('assoc[.first == "Lana"].last', context).then(function(res) {
console.log(res); // Output: Kane
});
// Do math
mozjexl.eval('age * (3 - 1)', context, function(err, res) {
console.log(res); // Output: 72
});
// Concatenate
mozjexl.eval('name.first + " " + name["la" + "st"]', context).then(function(res) {
console.log(res); // Output: Sterling Archer
});
// Compound
mozjexl.eval('assoc[.last == "Figgis"].first == "Cyril" && assoc[.last == "Poovey"].first == "Pam"', context)
.then(function(res) {
console.log(res); // Output: true
});
// Use array indexes
mozjexl.eval('assoc[1]', context, function(err, res) {
console.log(res.first + ' ' + res.last); // Output: Cyril Figgis
});
// Use conditional logic
mozjexl.eval('age > 62 ? "retired" : "working"', context).then(function(res) {
console.log(res); // Output: working
});
// Transform
mozjexl.addTransform('upper', function(val) {
return val.toUpperCase();
});
mozjexl.eval('"duchess"|upper + " " + name.last|upper', context).then(function(res) {
console.log(res); // Output: DUCHESS ARCHER
});
// Transform asynchronously, with arguments
mozjexl.addTransform('getStat', function(val, stat) {
return dbSelectByLastName(val, stat); // Returns a promise
});
mozjexl.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
mozjexl.addBinaryOp('_=', 20, function(left, right) {
return left.toLowerCase() === right.toLowerCase();
});
mozjexl.eval('"Guest" _= "gUeSt"').then(function(val) {
console.log(res); // Output: true
});
For Node.js or Web projects, type this in your project folder:
yarn add mozjexl
Access Mozjexl the same way, backend or front:
import mozjexl from 'mozjexl';
Operation | Symbol |
---|---|
Negate | ! |
Operation | Symbol |
---|---|
Add, Concat | + |
Subtract | - |
Multiply | * |
Divide | / |
Divide and floor | // |
Modulus | % |
Power of | ^ |
Logical AND | && |
Logical OR | || |
Comparison | Symbol |
---|---|
Equal | == |
Not equal | != |
Greater than | > |
Greater than or equal | >= |
Less than | < |
Less than or equal | <= |
Element in array or string | 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'}]
.
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 |
Type | Examples |
---|---|
Booleans | true , false |
Strings | "Hello "user"", 'Hey there!' |
Numerics | 6, -7.2, 5, -3.14159 |
Objects | {hello: "world!"} |
Arrays | ['hello', 'world!'] |
Parentheses work just how you'd expect them to:
Expression | Result |
---|---|
(83 + 1) / 2 | 42 |
1 < 3 && (4 > 2 || 2 > 4) | true |
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, 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 |
The power of Mozjexl 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 mozjexl.addTransform(name, function)
.
mozjexl.addTransform('split', function(val, char) {
return val.split(char);
});
mozjexl.addTransform('lower', function(val) {
return val.toLowerCase();
});
Expression | Result |
---|---|
"Pam Poovey"|lower|split(' ')[1] | poovey |
"password==guest"|split('=' + '=') | ['password', 'guest'] |