Package Exports
- json-expression-eval
- json-expression-eval/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (json-expression-eval) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
json-expression-eval (and rule engine)
A Fully typed Node.js module that evaluates a json described boolean expressions using dynamic functions and a given context.
Expressions can also be evaluated in a rule engine manner.
The module is strictly typed, ensuring that passed expressions are 100% valid at compile time.
This module is especially useful if you need to serialize complex expressions / rules (to be saved in a DB for example)
Installation
npm install json-expression-evalOr
yarn add json-expression-evalUsage
Please see tests and examples dir for more usages and examples (under /src)
import {evaluate, Expression, ExpressionHandler, validate, ValidationContext} from 'json-expression-eval';
import {Moment} from 'moment';
import moment = require('moment');
interface IExampleContext {
userId: string;
times: number | undefined;
date: Moment;
nested: {
value: number | null;
nested2: {
value2?: number;
value3: boolean;
};
};
}
type IExampleContextIgnore = Moment;
type IExampleFunctionTable = {
countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }) => boolean;
}
type IExampleExpression = Expression<IExampleContext, IExampleFunctionTable, IExampleContextIgnore>; // We pass Moment here to avoid TS exhaustion
const context: IExampleContext = {
userId: 'a@b.com',
times: 3,
date: moment(),
nested: {
value: null,
nested2: {
value3: true,
},
},
};
// For validation we must provide a full example context
const validationContext: ValidationContext<IExampleContext, IExampleContextIgnore> = {
userId: 'a@b.com',
times: 3,
date: moment(),
nested: {
value: 5,
nested2: {
value2: 6,
value3: true,
},
},
};
const functionsTable: IExampleFunctionTable = {
countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }): boolean => {
return ctx.times === undefined ? false : ctx.times >= min && ctx.times < max;
},
};
const expression: IExampleExpression = {
or: [
{
userId: 'a@b.com',
},
{
and: [
{
countRange: [2, 6],
},
{
'nested.nested2.value3': true,
},
],
},
],
};
// Example usage 1
const handler =
new ExpressionHandler<IExampleContext, IExampleFunctionTable, IExampleContextIgnore>(expression, functionsTable);
handler.validate(validationContext); // Should not throw
console.log(handler.evaluate(context)); // true
// Example usage 2
validate<IExampleContext, IExampleFunctionTable, IExampleContextIgnore>(expression, validationContext, functionsTable); // Should not throw
console.log(evaluate<IExampleContext, IExampleFunctionTable, IExampleContextIgnore>(expression, context, functionsTable)); // trueExpression
There are 4 types of operators you can use (evaluated in that order of precedence):
and- accepts a non-empty list of expressionsor- accepts a non-empty list of expressionsnot- accepts another expressions<user defined funcs>- accepts any type of argument and evaluated by the user defined functions and the given context.<compare funcs>- operates on one of the context properties and compares it to a given value.{property: {op: value}}- available ops:
gt- >gte- >=lt- <lte- <=eq- ===neq- !==regexp: RegExp- True if matches the compiled regular expression.regexpi: RegExp- True if matches the compiled regular expression with theiflag set.nin: any[]- True if not in an array of values. Comparison is done using the===operatorinq: any[]- True if in an array of values. Comparison is done using the===operatorbetween: readonly [number, number] (as const)- True if the value is between the two specified values: greater than or equal to first value and less than or equal to second value.
- available ops:
{property: value}- compares the property to that value (shorthand to the
eqop)
- compares the property to that value (shorthand to the
Nested properties in the context can also be accessed using a dot notation (see example above) In each expression level, you can only define 1 operator, and 1 only
Example expressions, assuming we have the user and maxCount user defined functions in place can be:
{
"or":[
{
"not":{
"user":"a@b.com"
}
},
{
"maxCount":1
},
{
"times": { "eq" : 5}
},
{
"country": "USA"
}
]
}Rule Engine
Please see tests and examples dir for more usages and examples (under /src)
import {ValidationContext, validateRules, evaluateRules, RulesEngine, Rule, ResolvedConsequence} from 'json-expression-eval';
import {Moment} from 'moment';
import moment = require('moment');
interface IExampleContext {
userId: string;
times: number | undefined;
date: Moment;
nested: {
value: number | null;
nested2: {
value2?: number;
value3: boolean;
};
};
}
type IExampleContextIgnore = Moment;
type IExamplePayload = number;
type IExampleFunctionTable = {
countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }) => boolean;
}
type IExampleRuleFunctionTable = {
userRule: (user: string, ctx: IExampleContext) => void | ResolvedConsequence<IExamplePayload>;
}
type IExampleRule = Rule<IExamplePayload, IExampleRuleFunctionTable, IExampleContext,
IExampleFunctionTable, IExampleContextIgnore>;
const context: IExampleContext = {
userId: 'a@b.com',
times: 3,
date: moment(),
nested: {
value: null,
nested2: {
value3: true,
},
},
};
// For validation we must provide a full example context
const validationContext: ValidationContext<IExampleContext, IExampleContextIgnore> = {
userId: 'a@b.com',
times: 3,
date: moment(),
nested: {
value: 5,
nested2: {
value2: 6,
value3: true,
},
},
};
const functionsTable: IExampleFunctionTable = {
countRange: ([min, max]: [min: number, max: number], ctx: { times: number | undefined }): boolean => {
return ctx.times === undefined ? false : ctx.times >= min && ctx.times < max;
},
};
const ruleFunctionsTable: IExampleRuleFunctionTable = {
userRule: (user: string, ctx: IExampleContext): void | ResolvedConsequence<number> => {
if (ctx.userId === user) {
return {
message: `Username ${user} is not allowed`,
custom: 543,
}
}
},
};
const rules: IExampleRule[] = [
{
condition: {
or: [
{
userId: 'a@b.com',
},
{
and: [
{
countRange: [2, 6],
},
{
'nested.nested2.value3': true,
},
],
},
],
},
consequence: {
message: ['user', {
ref: 'userId',
}, 'should not equal a@b.com'],
custom: 579,
},
},
{
userRule: 'b@c.com',
},
];
// Example usage 1
const engine = new RulesEngine<IExamplePayload, IExampleContext, IExampleRuleFunctionTable,
IExampleFunctionTable, IExampleContextIgnore>(functionsTable, ruleFunctionsTable);
engine.validate(rules, validationContext); // Should not throw
console.log(JSON.stringify(engine.evaluateAll(rules, context))); // [{"message":"user a@b.com should not equal a@b.com","custom":579}]
// Example usage 2
validateRules<IExamplePayload, IExampleContext, IExampleRuleFunctionTable,
IExampleFunctionTable, IExampleContextIgnore>(rules, validationContext, functionsTable, ruleFunctionsTable); // Should not throw
console.log(JSON.stringify(evaluateRules<IExamplePayload, IExampleContext, IExampleRuleFunctionTable,
IExampleFunctionTable, IExampleContextIgnore>(rules, context, functionsTable, ruleFunctionsTable, false))); // [{"message":"user a@b.com should not equal a@b.com","custom":579}]