A fully typed, JSON-serializable boolean expression evaluator and rule engine for Node.js
Need to store complex business rules in a database? Want to evaluate dynamic conditions without eval()? This library lets you:
- Serialize expressions as JSON - Store rules in databases, send them over APIs
- 100% type-safe - Full TypeScript support with compile-time validation
- Extensible - Add custom functions for domain-specific logic
- Debug easily -
evaluateWithReasontells you exactly why an expression matched
# npm
npm install json-expression-eval
# yarn
yarn add json-expression-eval
# pnpm
pnpm add json-expression-evalimport { evaluate } from 'json-expression-eval';
// Define your context (the data to evaluate against)
const context = {
user: { age: 25, role: 'admin' },
subscription: { plan: 'pro', daysLeft: 15 }
};
// Define your expression (can be stored as JSON!)
const expression = {
and: [
{ 'user.age': { gte: 18 } },
{ 'user.role': 'admin' },
{ 'subscription.daysLeft': { gt: 0 } }
]
};
// Evaluate
const result = await evaluate(expression, context, {});
console.log(result); // trueExpressions are plain JavaScript objects that can be serialized to JSON. They support:
| Operator | Description | Example |
|---|---|---|
and |
All conditions must be true | { and: [expr1, expr2] } |
or |
At least one condition must be true | { or: [expr1, expr2] } |
not |
Negates the condition | { not: expr } |
| Operator | Description | Example |
|---|---|---|
eq |
Equal (===) |
{ age: { eq: 25 } } |
neq |
Not equal (!==) |
{ status: { neq: 'inactive' } } |
gt |
Greater than | { price: { gt: 100 } } |
gte |
Greater than or equal | { age: { gte: 18 } } |
lt |
Less than | { quantity: { lt: 10 } } |
lte |
Less than or equal | { score: { lte: 100 } } |
inq |
In array | { color: { inq: ['red', 'blue'] } } |
nin |
Not in array | { status: { nin: ['banned', 'suspended'] } } |
between |
Between two values (inclusive) | { age: { between: [18, 65] as const } } |
regexp |
Matches regex | { email: { regexp: '^.*@gmail\\.com$' } } |
regexpi |
Matches regex (case-insensitive) | { name: { regexpi: '^john' } } |
exists |
Check if value exists (not null/undefined) | { middleName: { exists: true } } |
For simple equality checks, you can omit the operator:
// These are equivalent:
{ userId: { eq: 'abc123' } }
{ userId: 'abc123' }Access nested properties using dot notation:
{ 'user.address.city': 'New York' }
{ 'order.items.0.price': { gt: 50 } }Compare a property against another property in the context:
{
'cart.total': {
lte: { ref: 'user.balance' } // cart.total <= user.balance
}
}Perform calculations in comparisons:
{
'order.total': {
lte: {
op: '*', // Multiply
lhs: { ref: 'user.credit' }, // Left-hand side
rhs: 0.9 // Right-hand side (10% discount)
}
}
}Available operations: +, -, *, /, %, pow
Add domain-specific logic with custom functions:
import { evaluate, Expression, EvaluatorFuncRunOptions } from 'json-expression-eval';
// Define your function table type
type MyFunctions = {
isPremiumUser: (
minPurchases: number,
ctx: { purchases: number },
opts: EvaluatorFuncRunOptions<{}>
) => Promise<boolean>;
};
// Implement the functions
const functions: MyFunctions = {
isPremiumUser: async (minPurchases, ctx) => {
return ctx.purchases >= minPurchases;
}
};
// Use in expressions
const expression: Expression<MyContext, MyFunctions> = {
and: [
{ isPremiumUser: 10 }, // Custom function call
{ 'subscription.active': true }
]
};
await evaluate(expression, context, functions);For more complex scenarios, use the rule engine to evaluate multiple rules and get detailed results:
import { RulesEngine, Rule } from 'json-expression-eval';
type MyPayload = { discountPercent: number };
const rules: Rule<MyPayload, ...>[] = [
{
condition: {
and: [
{ 'user.tier': 'gold' },
{ 'cart.total': { gte: 100 } }
]
},
consequence: {
message: 'Gold member discount applied!',
custom: { discountPercent: 20 }
}
},
{
condition: { 'cart.total': { gte: 50 } },
consequence: {
message: 'Bulk discount applied!',
custom: { discountPercent: 10 }
}
}
];
const engine = new RulesEngine(functions, ruleFunctions);
const results = await engine.evaluateAll(rules, context, {});
// Returns all matching rules with their consequences| Function | Description |
|---|---|
evaluate(expr, ctx, funcs, opts?) |
Evaluate expression, returns boolean |
evaluateWithReason(expr, ctx, funcs, opts?) |
Evaluate and return the minimal matching sub-expression |
validate(expr, ctx, funcs, opts?) |
Validate expression structure against a context |
| Class | Description |
|---|---|
ExpressionHandler |
Reusable expression evaluator instance |
RulesEngine |
Rule engine for evaluating multiple rules |
Returns { result: boolean, reason: Expression } where reason is the minimal sub-expression that determined the result:
or= true: Returns{or: [firstTrueExpr]}or= false: Returns{or: [allExprs]}(all failed)and= false: Returns{and: [firstFalseExpr]}and= true: Returns{and: [allExprs]}(all passed)
For more examples, see the /src/examples and /src/test directories.
MIT - See LICENSE for details.