tomfrost / jexl Goto Github PK
View Code? Open in Web Editor NEWJavascript Expression Language: Powerful context-based expression parser and evaluator
License: MIT License
Javascript Expression Language: Powerful context-based expression parser and evaluator
License: MIT License
hi. is there any way to escape the dash (minus sign) character ?
jexl
.eval('foo-bar == "foobar"', {
"foo-bar": "foobar"
})
.then(console.log)
.catch(console.error);
results false
Does this library handle the comparison of date, and times?
for example
var context = {
birthdate: "01-01-1975',
todaysdate: '25-03-2017'
}
jexl.eval('birthdate < todaysdate', context).then(function(res){ });
Hi, this is a fantastic little library. Thanks for contributing it!
I've been playing with it and seeing if it'll work for us as a safe way of letting users define their own calculations which we can store in the database and apply to their own docs at runtime. Your Transforms feature is fantastic.
Using it, I thought I'd try binding a custom transform to lodash. It turns out that this can be pretty handy, and goes some way towards addressing Issue #25 (at least the example use case) and possibly #43. So I thought I'd share it here, and ask 2 questions:
The Transforms feature allows us to bind other 3rd-party libraries, for example Lodash :
const _ = require('lodash');
const jexl = require('jexl');
jexl.addTransform('_', (inputObj, lodashFuncName, ...arguments) => {
if(_.hasOwnProperty(lodashFuncName)){
return _[lodashFuncName].apply(this, [inputObj].concat(arguments));
}
});
Then you can use many of the lodash functions, for example:
const doc2 = { 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 }
jexl.evalSync('employees|_("meanBy","age")', doc2)
// output: 43.4
jexl.evalSync('employees|_("maxBy","age")', doc2)
// output: { first: 'Malory', last: 'Archer', age: 75 }
jexl.evalSync('employees|_("minBy","age")', doc2)
// output: { first: 'Cheryl', last: 'Tunt', age: 28 }
jexl.evalSync('employees|_("map","age")', doc2)
// output: [ 36, 75, 33, 45, 28 ]
// === you can combine them! ===
jexl.evalSync('employees|_("map","age")|_("sum")', doc2)
// output: 217
Hi! I have the need for "non-standard" accented letters for the identifiers. Would it be possible to include a support for those as well?
I tried it out by extending the regexps for the identifiers and is seems to work. I'll make a pull request for you to consider that.
Thanks,
Henrik.
I believe this happend due to StandardJS suggestions.
version 1.1.4 was comparing string variables and number variables with ==
and !=
'2'==2
is true'2'<=2
is true'2'!=2
is false'2'!=3
is trueversion 2.0.1 breaks this by using ===
and !==
'2'==2
is false'2'<=2
is true'2'!=2
is true'2'!=3
is trueWhen looking at how ``'2'<=2 is true
works you should revert back to use `==` and `!=`.
Is there anyway I can use Jexl synchronously ?
Is there a way I can pre compile an expression to save time during evaluation ?
Hi,
I need to evaluate an expression, but I need the result immediately, not wrapped into a promise. The evaluator is executed in a synchronous context and I cannot switch to async pattern. All used variables are ready, no promises.
Is this possible with the current version?
If not, are you planning to add this feature? How difficult is it to implement in current architecture, if I decide to add it by myself?
Thank you.
what is the difference between eval from Jexl, compared to eval from javascript?
Second question, is it really safe?
This issue was reported by one of my users: advanced-rest-client/arc-electron#102
The user put the following text into the input:
{
"regex1": "^([\\w][\\w\\s\\-]*[\\w]|[\\w])$",
"regex2": "^\\-?[1-9][0-9]*(\\.[0-9]+)?$",
"testParam3": "${placeholder}"
}
The result of parsing it with Jexl is:
{
"regex1": "^([\w][\\w\\s\\-]*[\\w]|[\\w])$",
"regex2": "^\-?[1-9][0-9]*(\\.[0-9]+)?$",
"testParam3": "someValueFromContext"
}
The problem is with \\
characters in 1st and 2nd lines. They got truncated but only the first occurance. At first I thought it's my implementation but after tracing the execution it gets truncated when calling Jexl API.
Is there something I can do to prevent this behavior? Or can it be fixed in Jexl?
Thanks
I used addUnaryOp to return the length of a filtered array :
jexl.addUnaryOp("length", array => { if (Array.isArray(array)) { return array.length; } return 0; });
When I build an expression like :
length objects[.myProperty in ["A","B"]]
Let's say I got this object to evaluate:
{ "objects": [ {"myProperty":"A"},{"myProperty":"B"},{"myProperty":"C"} ] }
The eval method will return 2 as result, but evalSync will evaluates the same expression to 3. It seems like the filtering part on the array vs my custom "length" are not executed with the same operation priorities depending if I use eval or evalSync.
When calculating the addition of two numbers
jexl.eval(that.formula, that.context, function (err, res) {
console.log(res);
});
where that.formula is
a+b
when the context is
{
a: 1.2,
b: 1.22
}
the result is 2.42
when the context is
{
a: 1.2,
b: 1.222
}
the result is 2.4219999999999997
and when I increase b by one more decimal place
{
a: 1.2,
b: 1.2222
}
the result is 2.4222
Hi, there! @TomFrost
thanks for your wonderful project (!!!!), which gives me a great favour in my projects!
but i got a problem today ...
when i eval 'length' of an empty array, error was thrown.
An example here:
const emptyArrayLength = jexl.eval('names.length', {names:[]}); // throw error: cannot read property 'length' of undefined
Shouldn't jexl return 0 ?
Use case:
Possible path:
jexl
instances.H Tom,
Thanks a ton for such a great library!!!
I am trying to do something similar to one given below.
const context = {
name: { first: 'Sterling', last: 'Archer' }
}
jexl.eval('name.first.toUpperCase()', context)
My use case is , i get the expression from some other source dynamically. I have few prototype methods that i need to execute as part of expression. Is there a way to do this ?
Right now, above snippet returns an error while trying to call toUpperCase on string .
Thank you.
Hey,
Considering this:
const jexl = require('jexl');
var context = {
name: {first: 'Sterling', last: 'Archer'},
assoc: [
{first: 'Lana', last: 'Kane'},
{first: 'Lana', last: 'Kane1'},
{first: 'Cyril', last: 'Figgis'},
{first: 'Pam', last: 'Poovey'}
],
age: 36
};
const exp = 'assoc["Lana" in .last]';
Evaluating this expression results Kane
, I was expecting the filter to result two objects and then extract from both the last name: ["Kane", "Kane1"]
(btw, without data extraction after filter it does result two objects)
Thoughts?
Thanks.
Since version 2.2.0. Null values in context are treated differently.
Example:
const jexl = require("jexl");
const context = {
data: {
author: null
}
};
const test = jexl.evalSync('data.author != null && data.author.id == "1"', context);
console.log(test);
In v2.1.1
variable test
is set to false
as it should be.
In v2.2.1
an error is thrown Error: Error: TypeError: Cannot read property 'id' of null
. And null check like so: data.author != null &&
does not help.
Weirdly enough if author
in context is set to undefined
, it works as expected by returning false
.
Shouldn't null
values be handled in similar way to undefined
values?
Hi @TomFrost,
I made a little interactive playground for Jexl: https://czosel.github.io/jexl-playground/
If you find it useful, I'd be happy to discuss how to move forward with it, for example by mentioning it in the docs, or even moving it to this repository?
It's implemented in Ember.js, you can find the code here: https://github.com/czosel/jexl-playground
Wanted to say thank you for this incredible library. My use case is for multiline expressions
Can we please replace the lexer line for whitespace with this?
This line of code appears to be responsible for the regex that tokenizes whitespace.
//whitespace
'[\\r\\n\\t ]+',
I genuinely have no idea how to do testing or such to verify the changes, and I could use some help with contributing a pull request for this library.
How do we use Date objects in Jexl?
There seems to be a priority problem when multiple binary expressions are used in a row involving dotted identifiers.
var jexl = require('jexl');
var context = {name: {first: 'Malory', last: 'Archer'}};
var expr = 'name.first == "Malory" && name.last == "Archer"';
jexl.eval(expr, context).catch(function(err) { console.error(err.stack); });
TypeError: Cannot read property 'type' of undefined
at /home/mythmon/scratch/jexl-spike/node_modules/jexl/lib/evaluator/Evaluator.js:53:22
at process._tickDomainCallback (internal/process/next_tick.js:129:7)
Digging around in the internals a bit, I got this AST from the parser:
var Lexer = require('jexl/lib/Lexer');
var Parser = require('jexl/lib/parser/Parser');
var grammar = require('jexl/lib/grammar').elements;
var tokens = new Lexer(grammar).tokenize(expr);
var parser = new Parser(grammar);
parser.addTokens(tokens);
var ast = parser.complete();
JSON.stringify(ast, null, 2);
{
"type": "BinaryExpression",
"operator": "==",
"left": {
"type": "Identifier",
"value": "last",
"from": {
"type": "Identifier",
"value": "name",
"from": {
"type": "BinaryExpression",
"operator": "&&",
"left": {
"type": "BinaryExpression",
"operator": "==",
"left": {
"type": "Identifier",
"value": "first",
"from": {
"type": "Identifier",
"value": "name"
}
},
"right": {
"type": "Literal",
"value": "Malory"
}
}
}
}
},
"right": {
"type": "Literal",
"value": "Archer"
}
}
That looks unbalanced to me. Specifically left.from.left
is missing a corresponding left.from.right
. This problem doesn't show up when using undotted identifiers.
This may already be supported, but if so, I couldn't find the syntax in the documentation. Currently, Jexl supports filtering using the data[.name == 'Austin'].id
syntax. I'd like to be able to basically map
an array, so that we can return a subset of properties.
Here are two examples of what I'm trying to accomplish:
Sample internal map
functionality:
const jexl = require('jexl');
// It would be great if this would return:
// [{ properties: { id: "abc" } }, { properties: { id: "def" } }]
const result = jexl.evalSync("data[.properties]", {
data: [
{
name: 'John',
properties: {
id: 'abc'
}
},
{
name: 'Jane',
properties: {
id: 'def'
}
}
]
});
Sample mapping of sub properties:
const jexl = require('jexl');
// [{ id: "abc" }, { id: "def" }]
const result = jexl.evalSync("data[.properties.id]", {
data: [
{
name: 'John',
properties: {
id: 'abc'
}
},
{
name: 'Jane',
properties: {
id: 'def'
}
}
]
});
Thoughts @TomFrost?
In the original project, there was a "ready to use" frontend script, in the form of a minified js file :
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>
I would be a really appreciated gesture, since not everyone uses node.js (or even likes it) and since it is pretty heavy to install/configure, even more since the "trendy tools" change every month or so...
Would it be possible for you to generate a "standalone" javascript file (minified or not) to be used in a frontend only context?
Is there any way to define functions in Jexl? Thank you for the hard work!
(requires enhancement to Lexer and Parser, maybe defaultGrammar)
We love your library. We are planning to use this for a project. It would be create for some background on the project or problem that motivated your interest and focus that lead to this?
Many if not most programming languages implement shortcuts for the logic operators or
and and
:
or
, if the left hand parameter is true
, the right hand parameter is not evaluated as the result is already determined to be true
and
, if the left hand parameter is false
, the right hand parameter is not evaluated, as the result is already determined to be false
This allows for some checking to see if for example a calculation even makes sense (think avoiding division by zero for example): foo != 0 && 3 / foo < 10
).
As I understand the code (https://github.com/TomFrost/Jexl/blob/master/lib/evaluator/handlers.js#L27), this is currently not implemented, causing every "branch" of an expression being evaluated, even if it's not needed to calculate a result.
Could we consider implementing this change?
Adding a transform with a name beginning with 'in' causes the Lexer not to recognize the transform name as a token. For example,
jexl.addTransform('indexOf', function(val, i) {
return val.indexOf(i);
});
jexl.eval("[1,2,3] | indexOf(1)", {}).then(function(res) {
console.log(res); // expecting 0
});
fails because indexOf is tokenized as 'in' and 'dexOf'.
var jexl = require('jexl')
var context = {
$a: 1
}
jexl.eval('$a', context)
$a
is a legal variable name in Javascript.
I found it is the problem of delimiter regex.
Could you fix it? Thx
var jexl = require("jexl");
jexl.eval("name.length", {name: ""}).then(console.log);
// undefined
jexl.eval("name.length == 0", {name: ""}).then(console.log);
// false
jexl.eval("name.length == undefined", {name: ""}).then(console.log);
// true
it('throws if transform fails', async () => {
inst.addTransform('abort', (val, args) => { throw new Error('oops') })
expect(inst.evalSync.bind(inst, '"hello"|abort')).toThrow(/oops/)
})
it('throws if one of the query fails', async () => {
inst.addTransform('q1', (val, args) => { throw new Error('oops') })
inst.addBinaryOp('is', 100, (val, args) => { return true })
expect(inst.evalSync.bind(inst, '"hello"|q1 is asdf')).toThrow(/oops/)
})
Does this library support function calls?
For example, given the following context
{
number1: 3,
number2: 5,
number3: 4
}
Can i use something like the following?
min(number1, number2, number3)
Currently, Jexl supports the ability to precompile by saving the Expression
object in memory. Example:
const jexl = require('jexl')
const compiled = jexl.compile('name')
compiled.evalSync({ name: 'John' })
I am using AWS Lambda, so the processes are short lived. I precompile my Jexl code and store the private expression._ast
object for later evaluation. My current hack is basically:
const jexl = require('jexl');
const ast = ...
const expression = '...'
const jexlExpression = new Expression(
jexl._getLang(),
expression
);
jexlExpression._ast = ast;
jexlExpression.evalSync({ ... });
We should consider exposing a public API for evaluating from a provided AST object.
Here is a possible API:
We could change Expression.prototype._getAst
to Expression.prototype.getAst
:
const ast = jexl.compile(...).getAst();
jexl.evalSyncFromAst(expression, ast, context)
jexl.evalFromAst(expression, ast, context)
Thoughts @TomFrost? I would be happy to work on this change if we can come up with an API.
Terrible title aside, here's some weirdness. Is it a bug? Hard to say, but it is odd.
'use strict';
var jexl = require('jexl'),
Bluebird = require('bluebird'),
haystack = {
foo: 'bar',
something: 'else'
};
function jexEval(needle) {
return jexl.eval(needle, haystack)
.catch((err) => {
return `Error: ${err.message}`;
});
}
Bluebird.props({
'51.5': jexEval('51.5'),
51: jexEval(51),
'foo': jexEval('foo'),
'hello': jexEval('hello')
})
.then((res) => {
console.log(res);
//{ '51': 'Error: str.split is not a function',
// '51.5': 51.5,
// foo: 'bar',
// hello: undefined }
});
Tested with jexl 1.1.2/3 on node 4.4.3
It would be better if I can choose either async or sync evaluation of jexl by option/ config. For example, I want to evaluate a json object with special syntax "${...}", eg. ${"2016-4-15"|toDate} where toDate is custom transform I added to jexl. I would grab the context between ${ and } by regular expression. The final object derived would be a Date object.
I would like to do this on React Component where the above would be one of the props, as the React Component lifecycle function is not async, I would like to do it and convert the props to state and use the state for the first time rendering.
I think it is just example case, there are many cases we may want to use Sync instead of Async.
Maybe can follow fs, jexl.evalSync and jexl.eval?
Hi,
thank you for this awesome library :)
I just noticed that:
const context = {
items: [
{id: 123, title: 'Hello world'},
{id: 456, title: 'Good bye world'}
],
my: {
itemId: 123
}
}
// The following will resolve with an empty array:
jexl.eval('items[.id == my.itemId]', context).then(console.log)
// While this will give the correct subset:
jexl.eval('items[.id == (my.itemId)]', context).then(console.log)
Hi,
can we have feature to intercept the expression before evaluating it, so we can do custom operations on it?
thanks in advanced.
The expression "value[.9supPjNuM4LbbXHXC != 't'].firstName" fails with
Error: Token 9 (literal) unexpected in expression: value[.9
Therefore I cannot filter my array of objects which each have a key '9supPjNuM4LbbXHXC', ie:
{
9supPjNuM4LbbXHXC: 'abc123',
firstName: 'bob'
}
How does one filter primitive arrays. ie,
var array = ['a','b','c']
jexl.eval("array[? == 'b']|tranform")
How do you reference the element rather than a property of the element?
First, I'd like to say how much I appreciate all of the hard work on this module. It's so awesome!
I ran into a case where the data I am passing to be evaluated actually includes dots in the properties.
Example:
const jexl = require('jexl');
const evaluated = jexl.evalSync('my.name', {
'my.name': 'Austin'
});
console.log(evaluated); // undefined
Is this something we should try to support? I haven't dug into the code much yet, but I could help work on making this change if it is something that we think is possible without too much effort.
I found that including jexl.min.js after including Dojo, jexl overwrites the Dojo require with is own, rendering all subsequent require statements of Dojo inoperable.
I also tried to use as a module, but the loader does not recognize it as a module. Any suggestions?
I need to access req.user in my transformers, what is the best way to do this? I love this project by the way, thank you!
I'm looking at using Jexl for templates, and I want to distingush between two cases:
The problem is that I get back undefined
in either case, so I can't tell them apart.
What I'd like is: 1) returns undefined but 2) returns null. All that is neccessary for that to happen is to change the Identifier handler to return null when the from
evaluates to null instead of returning undefined.
But, I get that perhaps not everyone wants the same semantics as me. But perhaps, given the customizability you already have in your language, you'd be amenable to a PR adding the option to customize the logic on this point?
Otherwise, it looks like I could get the functionality I want by a convoluted process of compiling the expression, rewriting the AST on the returned expression object, then evaluating that. But... that's really ugly. Or I could fork the whole project and modify that one piece, but I'd rather not if I don't have to.
Dear @TomFrost and community,
i recently stumbled upon this library, as i was searching for a javascript'y solution for JEXL
known from Java (e.g., http://commons.apache.org/proper/commons-jexl/ )
I must admit - this package looks awesome and i really like the approach. However, before using this package in my own project, i would like to ask some questions, which may be crucial for the application i am building:
How far away is version 2.0 of this package? I found respective branch (and PR) on GitHub. Yet, the master
and 2.0
branch are last updated in September 2017
. I also found a comment (from October '17), where you stated that you are approx 95% finish with the 2.0..
Is there support for "common" functions, like min / max / avg / sum / count / ...
in rules?
Is it possible to "extend" the functionality to add own "functions" (cf. Question 2)? For example, i want to have a checkIfXpplies(a, b, c, d)
function that may contain a more sophisticated logic.
Thank you very much for your time and effort on this project. I know, that providing open-source software (and its maintanance) may be quite frustrating - but keep the good work up! This package is really awesome and i can really see its use in various applications ๐
Cheers from Germany
Why is there only one unary operator (!
)? Unary plus is a common way to convert to number (like +[] === 0
).
I did not find examples of its use in the readme. Is it possible to build "not in" conditions?
const context = {
peoples: [
{ first: 'Lana', last: 'Kane', hobby: ['guitar', 'flowers'] },
{ first: 'Cyril', last: 'Figgis', hobby: ['computer', 'cats'] },
{ first: 'Pam', last: 'Poovey', hobby: ['poetry', 'dogs'] }
]
}
jexl.eval('!(peoples["dogs" in .hobby])', context);
/* result:
{ first: 'Lana', last: 'Kane', hobby: ['guitar', 'flowers'] },
{ first: 'Cyril', last: 'Figgis', hobby: ['computer', 'cats'] },
*/
Hi @TomFrost
I've been running into some issues with running Jexl 2.0 in older browsers that don't support ES6 yet (specifically IE 11). Many libraries are publishing transpiled (and often also bundled) code to npm. Would you be interested in providing something like that for Jexl as well?
I might be able to open a PR for this, but I figured I'd ask first if you're interested at all ๐
Hi, is there an expression to provide the entire top-level document when invoking a transform, rather enforcing specification of a sub-property by name?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.