JSON Expressions
A powerful JavaScript expression engine for JSON-based dynamic computations and function composition. JSON Expressions provides a declarative syntax for creating complex logic, transformations, and calculations that can be serialized, stored, and executed safely.
Perfect for configuration-driven applications, business rules engines, and anywhere you need dynamic logic represented as data.
Overview
JSON Expressions is built around several key principles:
- Serializable Logic: Express complex computations as JSON that can be stored, transmitted, and versioned
- Deterministic: All expressions produce consistent, predictable results - no randomness or side effects
- Composable: Build complex logic by combining simple expressions using
$pipe
- Extensible: Easily add custom expressions through packs and custom definitions
- Safe Evaluation: Controlled execution environment without the risks of
eval()
- Type-Aware: Rich set of expressions for different data types (numbers, strings, arrays, objects)
- Dual Execution Modes: Apply expressions to input data, or evaluate them standalone
When to Use JSON Expressions
Excellent fit for:
- Configuration-driven applications - Store complex logic as data in databases
- Domain experts who understand their rules - Semi-technical users comfortable with structured data (GIS analysts, statisticians, financial modelers)
- Business rules that change frequently - Avoid code deployments for logic updates
- Cross-platform logic sharing - Same rules run in frontend (testing) and backend (production)
- Multi-tenant SaaS applications - Different customers need different business logic
- Complex conditional logic - Beyond simple boolean flags but not warranting full programming languages
Poor fit for:
- Simple boolean flags or key-value configs - Simple key/value JSON objects are completely adequate
- Performance-critical hot paths - Direct JavaScript functions will be faster
- Logic that rarely changes - Code deployments may be simpler
Performance Characteristics
JSON Expressions is optimized for flexibility over raw execution speed. Expect:
- Development speed gains from eliminating deployment cycles
- Cross-platform consistency from shared logic evaluation
- Execution overhead compared to native JavaScript functions
- Good performance for business rules, data transformations, and configuration logic
- Consider caching for frequently-evaluated complex expressions
What Expressions Look Like
Expressions are JSON objects that describe computations to be performed. Each expression has a single key that identifies the expression type (prefixed with $) and a value that provides the parameters:
Simple comparison:
{
$gt: 18;
}
Object-based conditions:
{
$matches: {
age: { $gte: 18 },
status: { $eq: "active" },
"profile.verified": true
};
}
Conditional logic:
{
$case: {
value: { $get: "status" },
cases: [{ when: "active", then: { $get: "fullName" } }],
default: "Inactive user"
}
}
Data transformation:
{
$pipe: [
{ $get: "children" },
{ $filterBy: { age: { $gte: 4 } } },
{ $map: { $get: "name" } },
];
}
Apply vs Evaluate
JSON Expressions provides two distinct execution modes that serve different purposes:
apply(expression, inputData)
Applies the expression to input data. The expression acts like a function that receives the input data and transforms or tests it.
import { createExpressionEngine } from "json-expressions";
const engine = createExpressionEngine();
const expression = { $gt: 18 };
const inputData = 25;
const result = engine.apply(expression, inputData);
In apply mode, expressions receive the input data as context and operate on it:
{ $get: "name" } gets the "name" property from input data
{ $identity: null } returns the input data unchanged (identity function)
{ $gt: 18 } tests if input data is greater than 18
{ $filter: { $gte: 4 } } filters input array for items >= 4
evaluate(expression)
Evaluates the expression independently without input data. The expression is fully self-contained and produces a static result.
const expression = { $sum: [1, 2, 3, 4] };
const result = engine.evaluate(expression);
In evaluate mode, expressions contain all the data they need:
{ $get: { object: { "name": "Hadley" }, path: "name" } } gets property from the provided object
{ $identity: "value" } returns "value" unchanged
{ $gt: [25, 18] } tests if 25 > 18
{ $sum: [1, 2, 3] } calculates sum of the provided numbers
When to use which:
-
Use apply when you have input data and want to transform, filter, or test it
- Data processing pipelines:
engine.apply(expression, userData)
- Business rules:
engine.apply(rule, customerData)
- Validation:
engine.apply(validator, formData)
-
Use evaluate when you need static calculations or have all data in the expression
- Configuration values:
engine.evaluate({ $sum: [10, 20, 30] })
- Static computations:
engine.evaluate({ $gt: [userAge, minimumAge] })
- Template rendering:
engine.evaluate({ $get: { object: config, path: "apiUrl" } })
Rule of thumb: If your expression needs external input data, use apply. If it's self-contained, use evaluate. If still in doubt, try to use the evaluate form first. If you run into a wall, you probably need to switch over to apply.
$gt | { $gt: 18 } | { $gt: [25, 18] } |
$get | { $get: "name" } | { $get: { object, path: "name" } } |
$identity | { $identity: null } | { $identity: "value" } |
$sum | { $sum: null } | { $sum: [1, 2, 3] } |
$mean | { $mean: null } | { $mean: [85, 90, 88] } |
Key difference: Apply mode expressions operate on the input data, while evaluate mode expressions contain all the data they need.
Mode-Specific Examples
Apply Mode - Operating on input data:
const data = { scores: [85, 90, 88], threshold: 80 };
engine.apply({ $get: "scores" }, data);
engine.apply({ $gt: 80 }, 85);
engine.apply({ $mean: null }, [85, 90, 88]);
Evaluate Mode - Self-contained expressions:
engine.evaluate({ $get: { object: { name: "Hadley" }, path: "name" } });
engine.evaluate({ $gt: [85, 80] });
engine.evaluate({ $mean: [85, 90, 88] });
Common Mode Pitfalls
engine.evaluate({ $gt: 18 });
engine.evaluate({ $gt: [25, 18] });
engine.apply({ $gt: [25, 18] }, inputData);
engine.apply({ $gt: 18 }, 25);
Architecture Benefits
Cross-Platform Logic Execution
The dual-mode architecture enables the same expressions to run consistently across different environments:
const eligibilityRule = {
$matches: {
age: { $gte: 18 },
status: { $eq: "active" },
balance: { $gt: 0 },
},
};
const previewResult = frontendEngine.apply(eligibilityRule, userData);
const productionResult = backendEngine.apply(eligibilityRule, userData);
await database.saveRule("user-eligibility", eligibilityRule);
JSON Serialization Advantages
Because expressions are pure JSON, they can be:
- Stored in databases as configuration data
- Transmitted over HTTP for distributed evaluation
- Versioned and audited using standard data tools and git
- Validated with JSON Schema for correctness
- Generated programmatically by rule builders
- Cached and optimized by infrastructure
Expression Engine Flexibility
Different contexts can use different expression engines with tailored capabilities:
const userEngine = createExpressionEngine({ packs: [filtering] });
const adminEngine = createExpressionEngine({
packs: [filtering, projection, math],
});
const geoEngine = createExpressionEngine({
packs: [filtering],
custom: { $withinRadius, $intersects, $contains },
});
Production Usage
JSON Expressions powers production systems including:
- SpectraGraph - Unified query language across multiple data sources
- Business rules engines - Dynamic email triggers, pricing logic, access controls
Quick Start
Get up and running in 30 seconds:
import { createExpressionEngine } from "json-expressions";
const engine = createExpressionEngine();
const children = [
{ name: "Chen", age: 3 },
{ name: "Amara", age: 5 },
{ name: "Diego", age: 4 },
];
const schoolAge = engine.apply(
{
$pipe: [
{ $filter: { $matches: { age: { $gte: 5 } } } },
{ $map: { $get: "name" } },
],
},
children,
);
API Reference
Core Functions
Basic Usage
Users create engines with the packs they need:
import { createExpressionEngine } from "json-expressions";
const engine = createExpressionEngine();
const result = engine.apply(expression, data);
const staticResult = engine.evaluate(expression);
createExpressionEngine(config)
Creates a custom expression engine with specified configuration.
Parameters:
config.packs (Array, optional) - Array of expression pack objects to include
config.custom (Object, optional) - Custom expression definitions
config.includeBase (Boolean, default: true) - Whether to include base expressions. Note that $literal cannot be excluded or overwritten.
import { createExpressionEngine } from "json-expressions";
import { math } from "json-expressions/packs/math";
const mathEngine = createExpressionEngine({
packs: [math],
});
const customEngine = createExpressionEngine({
includeBase: false,
custom: {
$double: {
apply: (operand, inputData) => inputData * 2,
evaluate: (operand) => operand * 2,
},
},
});
Engine Methods
apply(expression, inputData)
Evaluates an expression against input data.
- expression (Object) - The expression to evaluate
- inputData (any) - The input data context for evaluation
- Returns: Result of the expression evaluation
evaluate(expression)
Evaluates an expression without input data (for static expressions).
- expression (Object) - The expression to evaluate
- Returns: Static result of the expression
isExpression(value)
Tests whether a value is a valid expression.
- value (any) - The value to test
- Returns: Boolean indicating if the value is an expression
expressionNames
Array of all available expression names in the engine.
Error Handling
JSON Expressions provides clear error messages for common issues:
Invalid Expressions
engine.apply({ $unknown: 5 }, 10);
engine.apply({ $notAnExpression: 5 }, 10);
Type Errors
engine.apply({ $get: 123 }, { name: "Chen" });
engine.apply({
$case: {
value: 5,
cases: [{ when: { $literal: "not boolean" }, then: "result" }],
},
});
Data Access Errors
engine.apply({ $prop: "name" }, null);
engine.evaluate({ $get: { path: "name" } });
Best Practices
- Use
$get with defaults for safe property access: { $get: "name" }
- Use
$literal for values that might be confused with expressions
- Test expressions with sample data before using in production
- Use
$debug (available via direct import) to inspect intermediate values in complex pipelines
Expression Packs
JSON Expressions organizes functionality into packs - curated collections of expressions for specific use cases.
Base Pack (Always Included)
The base pack contains near-universal expressions used across almost all scenarios. These expressions are included by default in every engine unless explicitly excluded.
- $and - Logical AND operation across multiple expressions
- $default - Returns first non-null/undefined value from array of expressions
- $filter - Filters array items based on a condition
- $filterBy - Filters arrays by object property conditions (combines $filter + $matches)
- $get - Retrieves a value from data using dot notation paths with optional defaults
- $identity - Returns input data unchanged in apply mode, or evaluates/returns the operand in evaluate mode
- $if - Conditional expression that evaluates different branches based on a condition
- $isPresent - Tests if a value is meaningful (not null or undefined)
- $isEmpty - Tests if a value is empty/absent (null or undefined)
- $exists - Tests if a property or path exists in an object
- $literal - Returns a literal value (useful when you need to pass values that look like expressions)
- $map - Transforms each item in an array using an expression
- $not - Logical NOT operation that inverts a boolean expression
- $or - Logical OR operation across multiple expressions
- $pipe - Pipes data through multiple expressions in sequence (left-to-right)
- $sort - Sorts arrays by property or expression with optional desc flag
- $matches - Primary tool for object-based conditions - tests objects against property patterns using dot notation and predicates
Comparison expressions:
- $eq - Tests equality using deep comparison
- $gt - Tests if value is greater than operand
- $gte - Tests if value is greater than or equal to operand
- $lt - Tests if value is less than operand
- $lte - Tests if value is less than or equal to operand
- $ne - Tests inequality using deep comparison
Available Packs
Beyond the base pack, you can import additional functionality as needed:
import { createExpressionEngine, math, string } from "json-expressions";
const engine = createExpressionEngine({
packs: [math, string],
});
Aggregation Pack
Statistical and aggregation functions for data analysis:
- $count - Count of items in an array
- $first - First item in an array
- $last - Last item in an array
- $max - Maximum value in an array
- $mean - Arithmetic mean (average) of array values
- $min - Minimum value in an array
- $sum - Sum of array values
Array Pack
Complete array manipulation toolkit:
- $all - Tests if all elements in an array satisfy a predicate
- $any - Tests if any element in an array satisfies a predicate
- $append - Appends an array to the end of another array
- $coalesce - Returns the first non-null value from an array
- $concat - Concatenates multiple arrays together
- $find - Returns first element that satisfies a predicate
- $flatMap - Maps and flattens array items
- $flatten - Flattens nested arrays to specified depth
- $groupBy - Groups array elements by a property or expression
- $join - Joins array elements into a string with a separator
- $pluck - Extracts property values from array of objects
- $prepend - Prepends an array to the beginning of another array
- $reverse - Returns array with elements in reverse order
- $skip - Skips first N elements of an array
- $take - Takes first N elements of an array
- $unique - Returns unique values from an array
Comparison Pack
Scalar comparison operations for filtering and validation:
- $between - Tests if value is between two bounds (inclusive)
- $has - Tests if object has property at specified path (supports dot notation)
- $in - Tests if value exists in an array
- $isPresent - Tests if value is meaningful (not null or undefined)
- $isEmpty - Tests if value is empty/absent (null or undefined)
- $exists - Tests if property or path exists in an object
- $nin - Tests if value does not exist in an array
Filtering Pack
Complete toolkit for WHERE clause logic and data filtering - combines field access, comparisons, logic, and pattern matching:
- $and, $or, $not - Boolean logic
- $eq, $ne, $gt, $gte, $lt, $lte - Basic comparisons
- $get - Field access with dot notation paths
- $in, $nin - Membership tests
- $matches - Primary tool for object conditions - handles complex property patterns with dot notation
- $isPresent, $isEmpty, $exists - Value and existence checks
- $matchesRegex, $matchesLike, $matchesGlob - Pattern matching
- $pipe - Chain multiple filtering operations
Perfect for building complex filters with a single import. Use $matches as your primary tool for object-based conditions:
import { createExpressionEngine, filtering } from "json-expressions";
const engine = createExpressionEngine({ packs: [filtering] });
const activeToddlers = engine.apply(
{
$matches: {
age: { $and: [{ $gte: 2 }, { $lte: 4 }] },
status: { $eq: "active" },
"guardian.contact.phone": { $isPresent: true },
"medical.allergies": { $nin: ["severe-nuts", "severe-dairy"] },
activity: { $nin: ["napping", "sick"] },
},
},
children,
);
Projection Pack
Complete toolkit for SELECT clause operations and data transformation - combines aggregation, array operations, string transforms, and conditionals:
- $get - Field access with dot notation paths
- $pipe - Chain multiple projection operations
- $select - Projects/selects specific properties from objects
- $count, $sum, $min, $max, $mean - Aggregation functions
- $map, $filter, $flatMap, $distinct - Array transformations
- $concat, $join, $substring, $uppercase, $lowercase - String/value operations
- $if, $case - Conditionals for computed fields
Perfect for transforming and projecting data with a single import:
import { createExpressionEngine, projection } from "json-expressions";
const engine = createExpressionEngine({ packs: [projection] });
const report = engine.apply(
{
$pipe: [
{ $get: "children" },
{ $filter: { $get: "active" } },
{
$map: {
name: { $get: "name" },
displayName: { $uppercase: { $get: "name" } },
ageGroup: {
$if: {
if: { $matches: { age: { $gte: 4 } } },
then: "Pre-K",
else: "Toddler",
},
},
activities: { $join: ", " },
},
},
],
},
daycareData,
);
Object Pack
Key-value manipulation and object operations:
- $fromPairs - Creates object from array of [key, value] pairs
- $keys - Returns array of object property names
- $merge - Merges multiple objects together
- $omit - Creates object excluding specified properties
- $pairs - Returns array of [key, value] pairs from object
- $pick - Creates object with only specified properties
- $prop - Gets property value from object by dynamic key
- $values - Returns array of object property values
Math Pack
Arithmetic operations and mathematical functions:
- $abs - Absolute value of a number
- $add - Addition operation
- $divide - Division operation
- $modulo - Modulo (remainder) operation
- $multiply - Multiplication operation
- $pow - Power/exponentiation operation
- $sqrt - Square root operation
- $subtract - Subtraction operation
String Pack
String processing and pattern matching:
- $lowercase - Converts string to lowercase
- $matchesRegex - Tests if string matches a regular expression
- $replace - Replaces occurrences of a pattern in a string
- $split - Splits a string into an array using a separator
- $substring - Extracts a portion of a string
- $trim - Removes whitespace from beginning and end of string
- $uppercase - Converts string to uppercase
Individual Expression Imports
Some expressions are available for direct import when you need them outside of packs:
Debug Expression
The $debug expression is useful for development and troubleshooting but isn't included in any pack by default. Import it directly when needed:
import { createExpressionEngine } from "json-expressions";
import { $debug } from "json-expressions/src/definitions/flow";
const engine = createExpressionEngine({
custom: { $debug },
});
const result = engine.apply(
{
$pipe: [
{ $get: "users" },
{ $debug: "After getting users" },
{ $filter: { active: true } },
{ $debug: "After filtering active" },
{ $map: { $get: "name" } },
],
},
data,
);
Any expression can be included with this method. Use it if you don't want the overhead of an entire pack.
Usage Examples
Building Blocks: Simple to Complex
JSON Expressions excel at composing simple operations into complex logic:
{
$gt: 5;
}
{
$and: [{ $gt: 5 }, { $lt: 10 }];
}
{
$pipe: [{ $get: "age" }, { $and: [{ $gt: 5 }, { $lt: 10 }] }];
}
{
$pipe: [
{ $filter: { $gte: 4 } },
{ $map: { $multiply: 2 } },
{ $sum: null },
];
}
Common Patterns
{
$matches: {
age: { $gte: 18 },
status: { $eq: "active" },
"account.balance": { $gt: 0 }
}
}
{
$if: {
if: { $matches: { age: { $gte: 18 }, status: "active" } },
then: "eligible",
else: "not eligible",
}
}
{
$matches: {
"user.plan": { $in: ["pro", "enterprise"] },
"user.signupDate": { $gte: "2024-01-01" },
"user.riskScore": { $lt: 0.3 }
}
}
{
$map: {
name: { $get: "name" },
isEligible: { $pipe: [{ $get: "score" }, { $gte: 75 }] },
category: {
$case: {
value: { $get: "age" },
cases: [
{ when: { $lt: 13 }, then: "child" },
{ when: { $lt: 20 }, then: "teen" },
],
default: "adult"
}
}
}
}
$matches: The Object Condition Workhorse
$matches is the go-to expression for any daycare-based filtering or validation. It combines property access with predicate evaluation in a clean, readable syntax:
{
$and: [
{ $pipe: [{ $get: "child.age" }, { $gte: 3 }] },
{ $pipe: [{ $get: "enrollment.status" }, { $eq: "active" }] },
{ $pipe: [{ $get: "guardian.emergency.phone" }, { $isPresent: true }] }
]
}
{
$matches: {
"child.age": { $gte: 3 },
"enrollment.status": { $eq: "active" },
"guardian.emergency.phone": { $isPresent: true }
}
}
Key features:
- Dot notation paths: Access nested properties with
"child.medical.allergies"
- Mixed predicates and literals: Combine
{ $gte: 3 } with "active"
- Implicit AND logic: All conditions must be true
- Performance: More efficient than separate property access + predicate chains
Basic Data Transformation
import { createExpressionEngine } from "json-expressions";
const engine = createExpressionEngine();
const daycareData = {
teacher: { name: "James", age: 46 },
children: [
{ name: "Chen", age: 4, activity: "playing" },
{ name: "Serafina", age: 5, activity: "reading" },
{ name: "Diego", age: 3, activity: "napping" },
],
};
const teacherName = engine.apply({ $get: "teacher.name" }, daycareData);
const kindergartenReady = engine.apply(
{
$pipe: [
{ $get: "children" },
{ $filter: { $gte: 5 } },
{ $map: { $get: "name" } },
],
},
daycareData,
);
Static Calculations
const totalMealCost = engine.evaluate({
$sum: [8.5, 12.75, 4.25],
});
Complex Business Logic
const activityRecommendation = engine.apply(
{
$case: {
value: { $get: "age" },
cases: [
{ when: 2, then: "Sensory play and simple puzzles" },
{ when: 3, then: "Art activities and story time" },
{ when: { $eq: 4 }, then: "Pre-writing skills and group games" },
{ when: { $gte: 5 }, then: "Early math and reading readiness" },
],
default: "Age-appropriate developmental activities",
},
},
{ age: 4 },
);
Custom Expressions
You can extend JSON Expressions with custom functionality. For comprehensive documentation on creating custom expressions, see Custom Expressions Guide.
import { createExpressionEngine } from "json-expressions";
const customEngine = createExpressionEngine({
custom: {
$isValidEmail: {
apply: (operand, inputData) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(inputData);
},
evaluate: (operand) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(operand);
},
},
$titleCase: {
apply: (operand, inputData) => {
return inputData
.toLowerCase()
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
},
evaluate: (operand) => {
return operand
.toLowerCase()
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
},
},
},
});
const isValid = customEngine.apply({ $isValidEmail: null }, "user@example.com");
const formatted = customEngine.evaluate({ $titleCase: "john doe" });
More Examples
import { createExpressionEngine } from "json-expressions";
import { object } from "json-expressions/packs/object";
const engine = createExpressionEngine({ packs: [object] });
const students = [
{ name: "Aisha", age: 3, scores: [85, 90, 88], active: true },
{ name: "Chen", age: 5, scores: [92, 87, 95], active: true },
{ name: "Diego", age: 3, scores: [78, 84, 91], active: false },
{ name: "Serafina", age: 6, scores: [88, 92, 85], active: true },
];
const activeOlderStudents = engine.apply(
{
$filterBy: {
active: { $eq: true },
age: { $lt: 6 },
},
},
students,
);
const studentNames = engine.apply({ $pluck: "name" }, students);
const studentsByAge = engine.apply({ $groupBy: "age" }, students);
const summaries = engine.apply(
{
$map: {
$select: {
name: { $get: "name" },
averageScore: { $pipe: [{ $get: "scores" }, { $mean: null }] },
isActive: { $get: "active" },
},
},
},
students,
);
const hasScores = engine.apply({ $has: "scores" }, students[0]);
const allScores = engine.apply(
{
$pipe: [{ $pluck: "scores" }, { $flatten: null }],
},
students,
);
const uniqueAges = engine.apply(
{
$pipe: [{ $pluck: "age" }, { $unique: null }],
},
students,
);
TypeScript Support
JSON Expressions includes comprehensive TypeScript definitions for type safety and better developer experience.
Basic Usage
import { createExpressionEngine, Expression } from "json-expressions";
const engine = createExpressionEngine();
const expression: Expression = { $gt: 18 };
const result: unknown = engine.apply(expression, 25);
if (engine.isExpression(someValue)) {
const result = engine.apply(someValue, data);
}
Custom Engine Types
import { createExpressionEngine, ExpressionEngine } from "json-expressions";
const engine: ExpressionEngine = createExpressionEngine({
custom: {
$myExpression: {
apply: (operand: string, inputData: any) => inputData + operand,
evaluate: (operand: string) => operand.toUpperCase(),
},
},
});
Expression Types
The library provides specific interfaces for each expression type:
import {
GetExpression,
PipeExpression,
FilterExpression,
} from "json-expressions";
const getExpr: GetExpression = { $get: "name" };
const pipeExpr: PipeExpression = {
$pipe: [{ $get: "children" }, { $filter: { $gte: 5 } }],
};
Installation
npm install json-expressions
License
MIT