A powerful, type-safe JSON-based rules engine for TypeScript/JavaScript applications. Define complex validation and business logic rules using simple JSON structures.
npm install @inixiative/json-rules
# or
yarn add @inixiative/json-rules
# or
bun add @inixiative/json-rules
- 🎯 Type-safe: Full TypeScript support with strict type checking
- 🔧 Flexible: 22 standard operators, 8 array operators, and 8 date operators
- 🌳 Composable: Nest rules with logical operators (all/any) and conditional logic (if-then-else)
- 📊 Array validation: Rich array validation with element-wise conditions
- 📅 Date handling: Comprehensive date comparison with timezone support
- 🔍 Path-based access: Reference values from anywhere in your data structure
- 💬 Custom errors: Every rule supports custom error messages
npm install json-rules
# or
yarn add json-rules
# or
bun add json-rules
import { check, Operator } from 'json-rules';
// Simple rule
const rule = {
field: 'age',
operator: Operator.greaterThanEqual,
value: 18,
error: 'Must be 18 or older'
};
const result = check(rule, { age: 21 }); // returns true
const result2 = check(rule, { age: 16 }); // returns "Must be 18 or older"
equal
- Exact equality checknotEqual
- Not equal checklessThan
- Less than comparisonlessThanEqual
- Less than or equalgreaterThan
- Greater than comparisongreaterThanEqual
- Greater than or equal
between
- Value within range (inclusive)notBetween
- Value outside range
in
- Value in arraynotIn
- Value not in arraycontains
- Array/string contains valuenotContains
- Array/string doesn't contain value
startsWith
- String starts with valueendsWith
- String ends with value
match
- Regex pattern matchnotMatch
- Regex pattern doesn't match
isEmpty
- Check if value is empty (null, undefined, "", [], {})notEmpty
- Check if value is not emptyexists
- Field exists (not undefined)notExists
- Field doesn't exist (undefined)
all
- All elements match conditionany
- At least one element matchesnone
- No elements matchatLeast
- At least X elements matchatMost
- At most X elements matchexactly
- Exactly X elements matchempty
- Array is emptynotEmpty
- Array has elements
before
- Date is before comparison dateafter
- Date is after comparison dateonOrBefore
- Date is on or beforeonOrAfter
- Date is on or afterbetween
- Date is between two datesnotBetween
- Date is outside rangedayIn
- Day of week is in listdayNotIn
- Day of week is not in list
Date comparisons are timezone-aware:
-
When condition value has no timezone (e.g.,
'2025-01-20'
), it's interpreted in the field's timezone:// Field: Jan 20 10:00 AM Sydney time { eventDate: '2025-01-20T10:00:00+11:00' } // Condition: on or after Jan 20 (interpreted as Jan 20 in Sydney) { dateOperator: 'onOrAfter', value: '2025-01-20' } // ✓ passes
-
When condition value has timezone (e.g.,
'2025-01-20T00:00:00Z'
), it's used as-is:// Condition: after midnight UTC specifically { dateOperator: 'after', value: '2025-01-20T00:00:00Z' }
-
Fields without timezone are treated as local time (UTC offset 0)
{
field: 'status',
operator: Operator.equal,
value: 'active'
}
// All conditions must pass (AND)
{
all: [
{ field: 'age', operator: Operator.greaterThanEqual, value: 18 },
{ field: 'hasLicense', operator: Operator.equal, value: true }
]
}
// At least one must pass (OR)
{
any: [
{ field: 'role', operator: Operator.equal, value: 'admin' },
{ field: 'isOwner', operator: Operator.equal, value: true }
]
}
{
if: { field: 'type', operator: Operator.equal, value: 'premium' },
then: { field: 'discount', operator: Operator.greaterThan, value: 0 },
else: { field: 'discount', operator: Operator.equal, value: 0 }
}
{
field: 'orders',
arrayOperator: ArrayOperator.all,
condition: {
field: 'total',
operator: Operator.lessThan,
value: 1000
}
}
{
field: 'expiryDate',
dateOperator: DateOperator.after,
value: '2024-12-31'
}
Compare fields against each other using paths:
{
field: 'confirmPassword',
operator: Operator.equal,
path: 'password' // Compare against another field
}
Use $.
prefix to reference the current array element:
{
field: 'items',
arrayOperator: ArrayOperator.all,
condition: {
field: 'price',
operator: Operator.lessThan,
path: '$.maxPrice' // Reference field on current array element
}
}
Every rule supports custom error messages:
{
field: 'email',
operator: Operator.match,
value: /^[^@]+@[^@]+\.[^@]+$/,
error: 'Please enter a valid email address'
}
const rule = {
all: [
// User must be active
{ field: 'status', operator: Operator.equal, value: 'active' },
// Age requirement
{ field: 'age', operator: Operator.between, value: [18, 65] },
// Must have at least one verified email
{
field: 'emails',
arrayOperator: ArrayOperator.any,
condition: { field: 'verified', operator: Operator.equal, value: true }
},
// Conditional premium features
{
if: { field: 'subscription', operator: Operator.equal, value: 'premium' },
then: {
field: 'features',
arrayOperator: ArrayOperator.all,
condition: { field: 'enabled', operator: Operator.equal, value: true }
}
}
]
};
const userData = {
status: 'active',
age: 25,
emails: [
{ address: 'user@example.com', verified: true },
{ address: 'alt@example.com', verified: false }
],
subscription: 'premium',
features: [
{ name: 'advanced', enabled: true },
{ name: 'analytics', enabled: true }
]
};
const result = check(rule, userData); // returns true
The main validation function.
- condition: The rule to evaluate
- data: The data to validate against
- context: Optional context (defaults to data)
- Returns:
true
if validation passes, error string if it fails
type Condition = Rule | ArrayRule | DateRule | All | Any | IfThenElse | boolean;
type Rule = {
field: string;
operator: Operator;
value?: any;
path?: string;
error?: string;
};
type ArrayRule = {
field: string;
arrayOperator: ArrayOperator;
condition?: Condition;
count?: number;
error?: string;
};
type DateRule = {
field: string;
dateOperator: DateOperator;
value?: any;
path?: string;
error?: string;
};
The engine throws errors for:
- Invalid array fields when using array operators
- Missing required parameters (e.g., count for atLeast)
- Invalid dates in date comparisons
- Primitive arrays with array operators (use
contains
orin
instead)
MIT