Skip to content

Repository files navigation

@codehz/json-expr

一个可序列化为 JSON 的表达式 DSL 库,提供类型安全的表达式构建、编译和求值。

npm version

特性

  • 🎯 类型安全 - 使用 TypeScript 泛型进行完整的编译时类型推导
  • 📦 可序列化 - 编译后的表达式为纯 JSON 格式,易于传输和存储
  • 🔧 灵活的表达式 - 支持任意 JavaScript 表达式,包括函数调用和对象属性访问
  • 高性能 - 优化的表达式编译和执行
  • 🧩 可组合 - 表达式可以相互组合,形成复杂的计算树
  • 🔄 短路求值 - 支持 &&||?? 和三元表达式的控制流优化
  • 📝 内联优化 - 自动内联只被引用一次的子表达式
  • 🔗 Proxy 变量系统 - 支持链式属性访问和方法调用,如 config.timeoutuser.profile.name
  • 🎛️ Lambda 表达式 - 类型安全的数组方法支持(map、filter、reduce 等)
  • 🛡️ 错误检测 - 编译时检测未定义变量和类型错误

快速开始

安装

bun install @codehz/json-expr

基本用法

import{variable,expr,compile,evaluate,t,lambda,wrap}from"@codehz/json-expr";// 定义类型化变量(使用 TypeScript 泛型)constx=variable<number>();consty=variable<number>();// 构建表达式constsum=expr({ x, y })("x + y");constproduct=expr({ x, y })("x * y");constresult=expr({ sum, product })("sum + product");// 编译表达式(可序列化为 JSON)constcompiled=compile(result,{ x, y });// => [["x", "y"], "$[0]+$[1]", "$[0]*$[1]", "$[2]+$[3]"]// 执行编译后的表达式constvalue=evaluate(compiled,{x: 2,y: 3});// => 11 (2+3 + 2*3 = 5 + 6 = 11)// 使用模板字符串constname=variable<string>();constgreeting=t`Hello, ${name}!`;// 使用 lambda 表达式constnumbers=variable<number[]>();constdoubled=numbers.map(lambda<[number],number>((n)=>expr({ n })("n * 2")));// 使用 wrap 包装静态值constpattern=wrap(/^[a-z]+$/i);constinput=variable<string>();constisValid=pattern.test(input);

核心概念

Variable(变量)

变量是表达式中的占位符,使用 TypeScript 泛型定义其类型。

constage=variable<number>();constname=variable<string>();constconfig=variable<{debug: boolean;timeout: number;}>();

Expression(表达式)

表达式对变量或其他表达式进行运算,使用字符串形式描述。

constx=variable<number>();consty=variable<number>();// 简单表达式constsum=expr({ x, y })("x + y");// 复杂表达式(可以使用 JS 语言特性)constabs=expr({ x })("Math.abs(x)");constconditional=expr({ x, y })("x > y ? x : y");constarray=expr({ x, y })("[x, y].filter(v => v > 0)");

CompiledData(编译数据)

编译后的表达式为 JSON 数组格式:

// 格式: [variableNames, expression1, expression2, ...]// 其中 $N 用于引用之前的变量或表达式constcompiled=compile(result,{ x, y });// [["x", "y"], "$[0]+$[1]", "$[0]*$[1]", "$[2]+$[3]"]

API 参考

variable<T>(): Variable<T>

创建一个类型化变量。

参数: 无(类型通过泛型参数 T 指定)

返回值: Variable 对象,支持属性访问和方法调用

示例:

constnum=variable<number>();conststr=variable<string>();constconfig=variable<{timeout: number}>();// 支持链式属性访问consttimeout=config.timeout;// 自动转换为表达式

expr<TContext>(context: TContext): (source: string) => Expression<TContext, TResult>

创建表达式,采用柯里化设计以支持完整的类型推导。

参数:

  • context - 上下文对象,包含变量和/或其他表达式的映射

返回值: 函数,接收表达式源码字符串并返回 Expression 对象

示例:

constx=variable<number>();consty=variable<number>();constsum=expr({ x, y })("x + y");constresult=expr({ sum, x })("sum * x");

compile<TResult>(expression: Expression<any, TResult>, variables: Record<string, Variable<any>> | Variable<any>[], options?: CompileOptions): CompiledData

将表达式树编译为可序列化的 JSON 结构。

参数:

  • expression - 要编译的表达式
  • variables - 表达式中使用的所有变量映射或数组
  • options - 编译选项(可选)
    • inline?: boolean - 是否启用内联优化,将只被引用一次的子表达式内联到使用位置(默认:true)

返回值: CompiledData 数组

示例:

constx=variable<number>();consty=variable<number>();constsum=expr({ x, y })("x + y");constproduct=expr({ x, y })("x * y");constresult=expr({ sum, product })("sum + product");constcompiled=compile(result,{ x, y });// [["x", "y"], "$[0]+$[1]", "$[0]*$[1]", "$[2]+$[3]"]// 禁用内联优化constnoInline=compile(result,{ x, y },{inline: false});// [["x", "y"], "$[0]+$[1]", "$[0]*$[1]", "$[2]+$[3]"]

evaluate<TResult>(data: CompiledData, values: Record<string, unknown>): TResult

执行编译后的表达式。

参数:

  • data - 编译后的表达式数据
  • values - 变量值映射,对应编译数据中的变量名顺序

返回值: 表达式计算结果

示例:

constx=variable<number>();consty=variable<number>();constsum=expr({ x, y })("x + y");constproduct=expr({ x, y })("x * y");constresult=expr({ sum, product })("sum + product");constcompiled=compile(result,{ x, y });constvalue=evaluate(compiled,{x: 5,y: 3});// => 23 ((5+3) + (5*3) = 8 + 15 = 23)

t(strings: TemplateStringsArray, ...values: unknown[]): Proxify<string>

使用标签模板函数创建包含变量的字符串表达式。

参数:

  • strings - 模板字符串的静态部分
  • values - 模板中插值的变量和表达式

返回值: 字符串类型的 Proxy Expression

示例:

constname=variable<string>();constcount=variable<number>();constgreeting=t`Hello, ${name}!`;constmessage=t`You have ${count} items.`;constcompiled=compile(greeting,{ name });constresult=evaluate(compiled,{name: "Alice"});// => "Hello, Alice!"

lambda<Args, R>(builder: LambdaBuilder<Args, R>): Lambda<Args, R>

创建类型安全的 lambda 表达式,用于数组方法(map、filter、reduce 等)。

参数:

  • builder - Lambda 构建函数,接收参数代理,返回函数体表达式

返回值: Lambda 表达式,可在数组方法中使用

示例:

import{lambda}from"@codehz/json-expr";// 单参数 lambdaconstnumbers=variable<number[]>();constdoubled=numbers.map(lambda<[number],number>((n)=>expr({ n })("n * 2")));constcompiled=compile(doubled,{ numbers });constresult=evaluate(compiled,{numbers: [1,2,3]});// => [2, 4, 6]// 多参数 lambda(reduce)constsum=numbers.reduce(lambda<[number,number],number>((acc,val)=>expr({ acc, val })("acc + val")),0);// 捕获外部变量constmultiplier=variable<number>();constscaled=numbers.map(lambda<[number],number>((n)=>expr({ n, multiplier })("n * multiplier")));

wrap<T>(value: T): Proxify<T>

将静态值包装为 Proxy Expression,使其可以像 Variable 一样调用方法和访问属性。

参数:

  • value - 要包装的静态值(支持原始值、对象、数组、Date、RegExp、BigInt、URL、Map、Set、TypedArray 等)

返回值: Proxy Expression,可以继续链式调用

示例:

// 包装 RegExpconstpattern=wrap(/^[a-z]+$/i);constinput=variable<string>();constisValid=pattern.test(input);constcompiled=compile(isValid,{ input });evaluate(compiled,{input: "hello"});// => trueevaluate(compiled,{input: "hello123"});// => false// 包装 Dateconstnow=wrap(newDate("2024-01-01"));constyear=now.getFullYear();// 包装数组conststaticNumbers=wrap([1,2,3,4,5]);constx=variable<number>();constdoubled=staticNumbers.map(lambda((n: number)=>expr({ n, x })("n * x")));// 包装对象constconfig=wrap({port: 8080,host: "localhost"});constport=config.port;// 直接访问属性// 包装 Mapconstmap=wrap(newMap([["a",1],["b",2],]));constkey=variable<string>();constvalue=map.get(key);// 链式调用consttext=wrap(" hello world ");constresult=text.trim().toUpperCase().replace("HELLO","HI");// => "HI WORLD"

高级用法

包装静态值(wrap)

wrap() 函数可以将任意静态值转换为 Proxy Expression,使其可以像 Variable 一样调用方法和访问属性。这在需要对常量值执行操作时非常有用。

基本用法:

// 不使用 wrap(传统方式)interfaceValidator{match(text: string,pattern: RegExp): boolean;}constvalidator=variable<Validator>();constresult=validator.match("hello",/^[a-z]+$/i);// 使用 wrap(推荐方式)constpattern=wrap(/^[a-z]+$/i);constinput=variable<string>();constresult=pattern.test(input);

支持的类型:

// 原始值constnum=wrap(42);conststr=wrap("hello");constbool=wrap(true);// Date 和 RegExpconstdate=wrap(newDate("2024-01-01"));constyear=date.getFullYear();constregex=wrap(/\d+/g);consttext=variable<string>();constmatches=text.match(regex);// BigIntconstbigNum=wrap(123456789n);constx=variable<bigint>();constsum=expr({ bigNum, x })("bigNum + x");// URLconsturl=wrap(newURL("https://example.com/path"));consthost=url.hostname;constport=url.port;// Map 和 Setconstmap=wrap(newMap([["key1",100],["key2",200],]));constkey=variable<string>();constvalue=map.get(key);constset=wrap(newSet([1,2,3]));constnum=variable<number>();consthas=set.has(num);// TypedArrayconstarr=wrap(newUint8Array([10,20,30]));constindex=variable<number>();constvalue=expr({ arr, index })("arr[index]");// 数组和对象constnumbers=wrap([1,2,3,4,5]);constmultiplier=variable<number>();constscaled=numbers.map(lambda((n: number)=>expr({ n, multiplier })("n * multiplier")));constconfig=wrap({port: 8080,host: "localhost"});constport=config.port;

链式调用:

consttext=wrap(" Hello, World! ");constresult=text.trim().toLowerCase().replace("world","universe");// => "hello, universe!"

与 variable 结合:

conststaticData=wrap({users: ["alice","bob","charlie"]});constindex=variable<number>();constusername=expr({ staticData, index })("staticData.users[index]");constcompiled=compile(username,{ index });evaluate(compiled,{index: 1});// => "bob"

Proxy 变量系统

variable() 创建的变量是 Proxy 对象,支持链式属性访问和方法调用,所有操作都会自动转换为表达式。

属性访问:

constconfig=variable<{timeout: number;retries: number;database: {host: string;port: number;};}>();// 链式属性访问consttimeout=config.timeout;// 自动转换为表达式constdbHost=config.database.host;// 支持嵌套访问constcompiled=compile(timeout,{ config });constresult=evaluate(compiled,{config: {timeout: 5000,retries: 3,database: {host: "localhost",port: 5432}},});// => 5000

方法调用:

constcalculator=variable<{add(a: number,b: number): number;multiply(x: number,y: number): number;}>();// 方法调用constsum=calculator.add(1,2);constproduct=calculator.multiply(5,3);// 链式方法调用constbuilder=variable<{setName(name: string): typeofbuilder;build(): {name: string};}>();constresult=builder.setName("test").build();// 编译并执行constcompiled=compile(sum,{ calculator });constvalue=evaluate(compiled,{calculator: {add: (a,b)=>a+b,multiply: (x,y)=>x*y,},});// => 3

数组方法:

数组变量支持所有标准数组方法,并自动处理类型推导:

constnumbers=variable<number[]>();constusers=variable<{id: number;name: string}[]>();// mapconstdoubled=numbers.map((n)=>expr({ n })("n * 2"));// filterconstactiveUsers=users.filter((u)=>expr({ u })("u.active"));// reduceconstsum=numbers.reduce(lambda<[number,number],number>((acc,val)=>expr({ acc, val })("acc + val")),0);// find, some, every, sort 等constfirstMatch=users.find((u)=>expr({ u })("u.id === 1"));consthasAdmins=users.some((u)=>expr({ u })("u.role === 'admin'"));constallActive=users.every((u)=>expr({ u })("u.active"));constsorted=numbers.toSorted(lambda<[number,number],number>((a,b)=>expr({ a, b })("a - b")));

内置全局对象

表达式中可以直接使用以下内置对象(无需在上下文中定义):

  • Math, JSON, Date, RegExp
  • Number, String, Boolean, Array, Object
  • undefined, NaN, Infinity
  • isNaN, isFinite, parseInt, parseFloat
constx=variable<number>();constsqrtExpr=expr({ x })("Math.sqrt(x)");constcompiled=compile(sqrtExpr,{ x });constresult=evaluate(compiled,{x: 16});// => 4

支持的运算符和语法

算术运算符:

  • +, -, *, /, %, ** (幂运算)

比较运算符:

  • ==, ===, !=, !==, <, >, <=, >=

逻辑运算符:

  • &&, ||, !, ?? (空值合并)

位运算符:

  • &, |, ^, ~, <<, >>, >>>

其他运算符:

  • ? : (三元表达式)
  • in (属性存在检查)
  • instanceof (类型检查)
  • typeof (类型检测)
  • ?. (可选链)
  • ?.() (可选调用)
  • ?.[] (可选元素访问)

语法特性:

  • 对象字面量:{ key: value, ... }
  • 数组字面量:[element1, element2, ...]
  • 箭头函数:(param) => expression
  • 函数调用:func(arg1, arg2, ...)
  • 成员访问:obj.prop, obj["prop"], arr[0]
  • 模板字面量(通过 t 标签函数)
  • 分组括号:(expression)

条件表达式

constscore=variable<number>();constgradeExpr=expr({ score })("score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'F'");constcompiled=compile(gradeExpr,{ score });constgrade=evaluate(compiled,{score: 85});// => "B"

数组和对象操作

constnumbers=variable<number[]>();constsumExpr=expr({ numbers })("numbers.reduce((a, b) => a + b, 0)");constcompiled=compile(sumExpr,{ numbers });constsum=evaluate(compiled,{numbers: [1,2,3,4,5]});// => 15

链式表达式组合

consta=variable<number>();constb=variable<number>();constsum=expr({ a, b })("a + b");constproduct=expr({ a, b })("a * b");constdifference=expr({ a, b })("a - b");constcomplex=expr({ sum, product, difference })("sum * product - difference");constcompiled=compile(complex,{ a, b });constresult=evaluate(compiled,{a: 2,b: 3});// => (2+3) * (2*3) - (2-3) = 5 * 6 - (-1) = 30 + 1 = 31

短路求值(控制流优化)

编译器支持为 &&||?? 和三元表达式生成短路求值代码,避免不必要的计算:

consta=variable<boolean>();constb=variable<boolean>();// 逻辑或短路constorExpr=expr({ a, b })("a || b");constcompiled=compile(orExpr,{ a, b });// 当 a 为 true 时,b 不会被求值// 编译数据包含控制流节点:// [["a", "b"], ["br", "$[0]", 1], "$[1]", ["phi"]]// 空值合并constx=variable<number|null>();consty=variable<number>();constcoalesce=expr({ x, y })("x ?? y");// 三元表达式constcondition=variable<boolean>();constresult=variable<number>();constalternative=variable<number>();constternary=expr({ condition, result, alternative })("condition ? result : alternative");

自动内联优化

编译器自动将只被引用一次的子表达式内联到使用位置,减少中间计算:

constx=variable<number>();consty=variable<number>();constsum=expr({ x, y })("x + y");constproduct=expr({ x, y })("x * y");constresult=expr({ sum, product })("sum + product");// 自动内联后,编译结果为:// [["x", "y"], "($[0]+$[1])+($[0]*$[1])"]// 而不是 [["x", "y"], "$[0]+$[1]", "$[0]*$[1]", "$[2]+$[3]"]constcompiled=compile(result,{ x, y });constvalue=evaluate(compiled,{x: 2,y: 3});// => 11

直接编译对象和数组

compile 函数支持直接编译包含 Proxy 的对象和数组:

constx=variable<number>();consty=variable<number>();constsum=expr({ x, y })("x + y");// 编译对象constobjCompiled=compile({result: sum,original: { x, y }},{ x, y });constobjResult=evaluate(objCompiled,{x: 10,y: 20});// => { result: 30, original: { x: 10, y: 20 }}// 编译数组constarrCompiled=compile([x,sum,100],{ x, y });constarrResult=evaluate(arrCompiled,{x: 5,y: 3});// => [5, 8, 100]

序列化和传输

编译后的数据可以轻松进行 JSON 序列化,适合网络传输或持久化存储:

// 编译表达式constcompiled=compile(result,{ x, y });// 序列化constjson=JSON.stringify(compiled);// "[["x","y"],"$[0]+$[1]","$[0]*$[1]","$[2]+$[3]"]"// 存储或传输...// 反序列化constdeserialized=JSON.parse(json);// 执行constvalue=evaluate(deserialized,{x: 5,y: 3});

编译数据格式

V1 格式(基础表达式)

基础格式为 JSON 数组:[variableNames, ...expressions]

// 输入constsum=expr({ x, y })("x + y");constcompiled=compile(sum,{ x, y });// 输出// [["x", "y"], "$[0]+$[1]"]// $[0] 引用 x,$[1] 引用 y

V2 格式(控制流节点)

启用短路求值时,生成包含控制流节点的格式:

// 输入constresult=expr({ a, b })("a || b");constcompiled=compile(result,{ a, b });// 输出// [// ["a", "b"],// ["br", "$[0]", 1], // 如果 $[0] 为 truthy,跳过 1 条指令// "$[1]", // 否则求值 $[1]// ["phi"] // 取最近求值结果// ]

控制流节点类型:

  • ["br", condition, offset] - 条件跳转,条件为真时跳过 offset 条指令
  • ["jmp", offset] - 无条件跳转,跳过 offset 条指令
  • ["phi"] - 取最近求值结果(用于合并分支)

错误处理

编译时错误

编译器会检测并报告以下错误:

constx=variable<number>();consty=variable<number>();// 错误:引用未定义的变量constinvalid=expr({ x, y })("x + y + z");compile(invalid,{ x, y });// => Error: Undefined variable(s): z// 错误:变量名冲突constxy=variable<number>();constconflict=expr({ xy, x })("xy + x");// 正确处理:编译器能区分 xy 和 xconstcompiled=compile(conflict,{ xy, x });// => [["xy", "x"], "$[0]+$[1]"]

运行时错误

求值器会验证输入并报告运行时错误:

constx=variable<number>();consty=variable<number>();constsum=expr({ x, y })("x + y");constcompiled=compile(sum,{ x, y });// 错误:缺少必需变量evaluate(compiled,{x: 2});// => Error: Missing required variable: y// 错误:无效的编译数据evaluate([],{x: 1});// => Error: Invalid compiled data: must have at least variable names

类型安全

项目充分利用 TypeScript 的类型系统进行编译时检查和类型推导:

constx=variable<number>();consty=variable<string>();// 类型错误会在编译时捕获// const invalid = expr({ x, y })("z + y"); // Error: 'z' not in contextconstvalid=expr({ x })("-x");// 编译器推导为 number

实际应用示例

动态表单验证规则

constformData=variable<{username: string;password: string;confirmPassword: string;age: number;}>();// 创建验证规则表达式constisUsernameValid=expr({ formData })("formData.username.length >= 3 && formData.username.length <= 20");constisPasswordValid=expr({ formData })("formData.password.length >= 8 && /[A-Z]/.test(formData.password)");constdoPasswordsMatch=expr({ formData })("formData.password === formData.confirmPassword");constisAgeValid=expr({ formData })("formData.age >= 18 && formData.age <= 120");constisFormValid=expr({
isUsernameValid,
isPasswordValid,
doPasswordsMatch,
isAgeValid,})("isUsernameValid && isPasswordValid && doPasswordsMatch && isAgeValid");// 编译一次,多次执行constcompiled=compile(isFormValid,{ formData });// 在表单输入时实时验证evaluate(compiled,{formData: {username: "john_doe",password: "Secure123",confirmPassword: "Secure123",age: 25,},});// => true

数据转换管道

constrawData=variable<any[]>();constconfig=variable<{minValue: number;maxValue: number;transform: (x: number)=>number;}>();// 构建数据处理管道constfiltered=rawData.filter(lambda<[any],boolean>((item)=>expr({ item, config })("item.value >= config.minValue && item.value <= config.maxValue")));consttransformed=filtered.map(lambda<[any],number>((item)=>expr({ item, config })("config.transform(item.value)")));constsorted=transformed.toSorted(lambda<[number,number],number>((a,b)=>expr({ a, b })("a - b")));constpipeline=compile(sorted,{ rawData, config });// 执行数据处理constresult=evaluate(pipeline,{rawData: [{value: 10},{value: 5},{value: 20},{value: 15}],config: {minValue: 8,maxValue: 18,transform: (x: number)=>x*2},});// => [10, 20, 30] (5 被过滤,10*2=20, 15*2=30, 20 被过滤)

规则引擎

// 定义规则条件constuser=variable<{age: number;role: string;balance: number;}>();constisEligible=expr({ user })("(user.age >= 18 && user.age <= 65) && (user.role === 'premium' || user.balance > 10000)");constdiscountRate=expr({ user, isEligible })("isEligible ? (user.role === 'premium' ? 0.2 : 0.1) : 0");construle=compile(discountRate,{ user });// 应用规则constdiscount=evaluate(rule,{user: {age: 30,role: "premium",balance: 5000},});// => 0.2 (20% 折扣)

性能考虑

  • 编译时间:编译过程涉及依赖分析和拓扑排序,通常快速完成
  • 执行时间:表达式通过 new Function() 编译为原生 JavaScript,执行性能接近原生代码
  • 内存占用:编译数据为纯 JSON,占用空间小,适合在网络上传输
  • 缓存机制:求值器缓存已编译的函数,重复执行时性能更优

最佳实践

  1. 编译一次,多次执行:对于重复使用的表达式,先编译后多次求值

    constcompiled=compile(expression,variables);// 缓存 compiled,多次调用 evaluateevaluate(compiled,values1);evaluate(compiled,values2);
  2. 利用短路求值:短路求值已默认启用,对于条件表达式可以避免不必要的计算

  3. 利用自动内联:编译器会自动内联只引用一次的子表达式,无需手动优化

  4. 优先使用 Proxy 链式调用:对于对象属性访问,使用 config.timeoutexpr({ config })("config.timeout") 更简洁且类型更安全

项目结构

src/
├── index.ts # 导出入口
├── variable.ts # variable<T>() 函数
├── expr.ts # expr() 函数
├── template.ts # t() 标签模板函数
├── lambda.ts # lambda() 函数(数组方法支持)
├── compile.ts # 编译器(内联优化、短路求值)
├── evaluate.ts # 运行时求值
├── parser.ts # 表达式 AST 解析器
├── type-parser.ts # TypeScript 类型级表达式解析
├── proxy-variable.ts # Proxy 变量实现
├── proxy-metadata.ts # Proxy 元数据管理
└── types.ts # 类型定义(Variable、Expression、Lambda 等)

开发

安装依赖

bun install

运行测试

bun test

代码检查

bun run lint
bun run type-check

代码格式化

bun run format

许可证

MIT

贡献

欢迎提交 Issue 和 Pull Request!

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages