djexl-js
v1.5.0
Published
Dynamic javascript Expression Language: Powerful context-based expression parser and evaluator.
Downloads
5
Maintainers
Readme
Djel (djexl-js)
Dynamic javascript Expression Language: 一个简单的表达式解析求值器.
Djel
is a fork of Jexl.
快速开始
const Djel = require('djexl-js').default;
const djel = Djel();
// add Transform
djel.addTransforms({
upper: (val) => val.toUpperCase(),
find: (arr, by) => arr.find(by),
});
const variables = {
name: { first: 'Sterling', last: 'Archer' },
assoc: [
{ first: 'Lana', last: 'Kane' },
{ first: 'Cyril', last: 'Figgis' },
{ first: 'Pam', last: 'Poovey' },
],
age: 36,
};
let res;
// find in an array
res = djel.evaluate('assoc|find(@.first == "Lana").last', variables);
console.log(res); // Output: Kane
// Do math
res = djel.evaluate('age * (3 - 1)', variables);
console.log(res); // Output: 72
// Concatenate
res = djel.evaluate('name.first + " " + name["la" + "st"]', variables);
console.log(res); // Output: Sterling Archer
// Compound
res = djel.evaluate('assoc|find(@.last == "Figgis").first == "Cyril" && assoc|find(@.last == "Poovey").first == "Pam"', variables);
console.log(res); // Output: true
// Use array indexes
res = djel.evaluate('assoc[1]', variables);
console.log(res.first + ' ' + res.last); // Output: Cyril Figgis
// Use conditional logic
res = djel.evaluate('age > 62 ? "retired" : "working"', variables);
console.log(res); // Output: working
res = djel.evaluate('"duchess"|upper + " " + name.last|upper', variables);
console.log(res); // Output: DUCHESS ARCHER
// Add your own operators
// Here's a case-insensitive string equality
djel.addBinaryOps({
'_=': {
priority: 30,
fn: (left, right) => left.toLowerCase() === right.toLowerCase(),
},
});
res = djel.evaluate('"Guest" _= "gUeSt"');
console.log(res); // Output: true
安装使用
yarn add djexl-js
import Djel from 'djexl-js';
注入变量
求值时,可以注入变量。如
const variables = { age: 10 };
djel.evaluate('age * (3 - 1)', variables); // => 20
// 或者
const expression = djel.compile('age * (3 - 1)');
expression.evaluate(variables); // => 20
一元操作符
| 操作符 | 符号 |
|-----|:---:|
| 取反 | !
|
| 加号 | +
|
| 减号 | -
|
二元操作符
| 操作符 | 符号 |
|----------------|:------------:|
| 加号,字符串/数组/对象拼接 | +
|
| 减 | -
|
| 乘 | *
|
| 除 | /
|
| 整除 | //
|
| 取模 | %
|
| 指数 | ^
|
| 逻辑与 | &&
|
| 逻辑或 | || |
| 空值合并 | ??
|
+
支持数组/对象拼接
| 表达式 | 结果 |
|---------------|-------------|
| [1,2]+[3,4]
| [1,2,3,4]
|
| {x:1}+{y:2}
| {x:1,y:2}
|
比较
| 操作符 | 符号 |
|------|:----:|
| 相等 | ==
|
| 不等 | !=
|
| 大于 | >
|
| 大于等于 | >=
|
| 小于 | <
|
| 小于等于 | <=
|
| in | in
|
- 关于
in
:
in
操作符可以检查字字符串,如:"Cad" in "Ron Cadillac"
。
也可以用于检查元素是否在数组中,如:"coarse" in ['fine', 'medium', 'coarse']
,但是这个判断是使用引用比较,因此 {a: 'b'} in [{a: 'b'}]
的结果是 false
。
三元表达式
| 表达式 | 结果 |
|-------------------------------------|------------|
| "" ? "Full" : "Empty"
| "Empty"
|
| "foo" in "foobar" ? "Yes" : "No"
| "Yes"
|
| {agent: "Archer"}.agent ?: "Kane"
| "Archer"
|
类型
| 类型 | 示例 |
|---------|:----------------------------------:|
| Boolean | true
, false
|
| String | "Hello \"user\""
, 'Hey there!'
|
| Number | 6
, -7.2
, 5
, -3.14159
|
| Object | {hello: "world!"}
|
| Array | ['hello', 'world!']
|
展开语法
可以在构造数组时,将数组表达式或者 string 在语法层面展开;可以在构造对象时,将对象表达式按 key-value 的方式展开。
| 表达式 | 结果 |
|--------------------------|:--------------------|
| [1,...[2,3],4]
| [1,2,3,4]
|
| [1,'23',4]
| [1,'2','3',4]
|
| {a:1,...{b:2,c:3},d:4}
| {a:1,b:2,c:3,d:4}
|
分组
小括号 ()
按照你预期的方式使用即可。
| 表达式 | 结果 |
|-------------------------------------|:-------|
| (83 + 1) / 2
| 42
|
| 1 < 3 && (4 > 2 || 2 > 4) | true
|
标识符
使用变量名访问变量,使用 .
或 []
访问对象属性值。
可选链运算符(?.
?.[]
?.()
)在引用为空 (null
或者 undefined
) 的情况下不会引起错误,该表达式短路返回 undefined
。
示例变量 :
const vairables = {
name: {
first: "Malory",
last: "Archer"
},
exes: [
"Nikolai Jakov",
"Len Trexler",
"Burt Reynolds"
],
lastEx: 2
}
| 表达式 | 结果 |
|---------------------|-------------------|
| name.first
| "Malory"
|
| name["first"]
| "Malory"
|
| name['la' + 'st']
| "Archer"
|
| exes[2]
| "Burt Reynolds"
|
| exes[lastEx - 1]
| "Len Trexler"
|
| exes[-1]
| "Burt Reynolds"
|
| exes[-2]
| "Len Trexler"
|
| foo?.bar.baz
| undefined
|
- 关于
[-1]
:
你可以使用负数在数组或字符串尾部获取元组或字符。如 a[-1]
表示最后一个元素或者字符。
定义变量
你可以使用 def
关键词定义变量,改变量有一个局部作用域,它也可以覆盖通过 variables
设置的变量。
| 表达式 | 结果 |
|-----------------------------------------------------------------------------|-----|
| def a = 1; def b = a + 1; a + b
| 3 |
| def a = 1; (true ? (def a = 10; a) : 0) + a
| 11 |
| 使用变量: const variables = { a: 1 }
(true ? (def a = 10; a) : 0) + a
| 11 |
函数调用
你可以像在 js 中一样调用函数,但是该函数必须定义在 variables
中。
比如变量如下:
const variables = { foo: 10, fns: { half: (v) => v / 2 } };
| 表达式 | 结果 |
|------------------------|-----|
| fns.half(foo) + 3
| 8 |
| fns["half"](foo) + 3
| 8 |
管道
管道是函数调用的语法糖。
形如 fn(a)
的函数调用可以简写成 a|fn
或者 a|fn()
的方式;
形如 fn(a,b,c)
的函数调用可以简写成 a|fn(b,c)
的方式。
这对多次函数调用非常有帮助,如 fn3(fn2(fn1(v)))
可以写成 v|fn1|fn2|fn3
。
但是需要注意,v|a.b.c
等价于 a(v).b.c
而不是 a.b.c(v)
;a.b.c(v)
的管道形式应该是 v|(a.b.c)
。
特殊函数注入方式
除了使用 variables
的方式注入函数外,还可以使用 djel.addTransforms
注入函数,如:
djel.addTransforms({
split: (var, char) => val.split(char),
lower: (val) => val.toLowerCase(),
});
| 表达式 | 结果 |
|--------------------------------------------|-------------------------|
| "Pam Poovey"|lower|split(' ')[1] | "poovey"
|
| "password==guest"|split('==') | ['password', 'guest']
|
| split("password==guest", '==')
| ['password', 'guest']
|
函数定义
定义函数的形式是 fn () => expression
或者 fn (a, b, c) => expression
。
示例:
const variables = {
users: [
{ age: 18, name: "Nikolai Jakov" },
{ age: 17, name: "Len Trexler" },
{ age: 19, name: "Burt Reynolds" },
],
}
djel.addTransforms({
filter: (arr, by) => arr.filter(by),
map: (arr, by) => arr.map(by),
sum: (arr, by) => arr.reduce((s, i) => s + (by(i) || 0), 0),
});
| 表达式 | 结果 |
|-------------------------------------------|--------------------------------------|
| users|filter(fn(a)=>a.age<18) | [{ age: 17, name: "Len Trexler" }]
|
| users|map(fn(a)=>a.age) | [18,17,19]
|
| users|sum(fn(a)=>a.age)/users.length | 18
|
| filter(users,fn(a)=>a.age<18)
| [{ age: 17, name: "Len Trexler" }]
|
| map(users,fn(a)=>a.age)
| [18,17,19]
|
| sum(users,fn(a)=>a.age)/users.length
| 18
|
简版函数定义
可以使用 @
@0
@1
~ @9
的特殊标识符来定义一个简版函数。
@
@0
表示第 0 个函数参数,@1
~ @9
分别表示 第 1 ~ 9 个函数参数。比如:@.x + @1
表示 fn (a, b) => a.x + b
。
示例(变量和注入函数同"函数定义"的示例):
| 表达式 | 结果 |
|------------------------------------|--------------------------------------|
| users|filter(@.age<18) | [{ age: 17, name: "Len Trexler" }]
|
| users|map(@.age) | [18,17,19]
|
| users|sum(@.age)/users.length | 18
|
| filter(users,@.age<18)
| [{ age: 17, name: "Len Trexler" }]
|
| map(users,@.age)
| [18,17,19]
|
| sum(users,@.age)/users.length
| 18
|
API
Djel
使用 Djel
可以创建一个实例,在这个实例你可以单独注入函数,定义、删除操作符等。
const djel = Djel()
evaluate
evaluate: (exp: string, variables?: any) => any
计算一个表达式,variables
是可选的。
compile
compile: (exp: string) => {
evaluate: (context?: any) => any;
}
你可以先编译一个表达式,之后使用编译结果在不同变量上进行求值。如:
const expression = djel.compile('a+b');
expression.evaluate({ a: 1, b: 2 }); // => 3
expression.evaluate({ a: 3, b: 4 }); // => 7
addBinaryOps
addBinaryOps: (binaryOps: Record<string, {
priority: number;
fn: (left: any, right: any) => any;
}>) => void
在 Djel
实例中添加二元操作符。二元操作符需要考虑其左值和右值,如 "+"
或 "=="
。
priority
属性决定了该操作符的优先级。
内置操作符的优先级如下表(见源码 src/grammar.ts
):
| 优先级 | 符合 | 操作符 |
|:---:|:-----------------------------------:|-----------|
| 10 | || ??
| 逻辑或,空值合并 |
| 20 | &&
| 逻辑与 |
| 30 | ==
!=
| 相等 |
| 40 | <=
<
>=
>
in
| 比较 |
| 50 | +
-
| 加、拼接、减 |
| 60 | *
/
//
%
| 乘、除、整除、取余 |
| 70 | ^
| 指数 |
| 80 | | | 管道 |
| 90 | !
+
-
| 一元操作符 |
| 100 | []
.
()
?.[]
?.
?.()
| 属性访问,函数调用 |
addUnaryOps
addUnaryOps: (unaryOps: Record<string, {
priority: number;
fn: (left: any) => any;
}>) => void
在 Djel
实例中添加一元操作符。一元操作符只需要考虑其右值,如 "!"
。
priority
属性决定了该操作符的优先级。
addTransforms
addTransforms: (transforms: Record<string, Function>) => void
在 Djel
实例中注入函数。
removeOp
removeOp: (operator: string) => void
在 Djel
实例中移除操作符。如 djel.removeOp('^')
可以移除指数操作符。
removeTransform
removeTransform: (transformName: string) => void
在 Djel
实例中移除注入的函数。
License
Djel
is licensed under the MIT license. Please see LICENSE.txt
for full
details.