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
- 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
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 }
Logical expressions:
{ "$and": [
{ "$gte": 18 },
{ "$lt": 65 }
]}
Conditional logic:
{ "$if": {
"if": { "$eq": "active" },
"then": { "$get": "fullName" },
"else": "Inactive User"
}}
Data transformation:
{ "$pipe": [
{ "$get": "children" },
{ "$filter": { "$gte": 4 } },
{ "$map": { "$get": "name" } }
]}
Apply vs Evaluate: The Critical Difference
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 { defaultExpressionEngine } from "json-expressions";
const expression = { $gt: 18 };
const inputData = 25;
const result = defaultExpressionEngine.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
{ $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 = defaultExpressionEngine.evaluate(expression);
In evaluate mode, expressions contain all the data they need:
{ $get: { object: data, path: "name" } } gets property from the provided object
{ $gt: [25, 18] } tests if 25 > 18
{ $sum: [1, 2, 3] } calculates sum of the provided numbers
When to use which:
- Use apply for data transformation pipelines, filtering, validation, and business rules
- Use evaluate for static calculations, configuration values, and self-contained computations
Quick Start
Get up and running in 30 seconds:
import { defaultExpressionEngine } from "json-expressions";
const children = [
{ name: "Chen", age: 3 },
{ name: "Amara", age: 5 },
{ name: "Diego", age: 4 }
];
const schoolAge = defaultExpressionEngine.apply(
{
$pipe: [
{ $filter: { $gte: 5 } },
{ $map: { $get: "name" } }
]
},
children
);
API Reference
Core Functions
defaultExpressionEngine
Pre-configured expression engine with base expressions included.
import { defaultExpressionEngine } from "json-expressions";
const result = defaultExpressionEngine.apply(expression, data);
const staticResult = defaultExpressionEngine.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
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
defaultExpressionEngine.apply({ $unknown: 5 }, 10);
defaultExpressionEngine.apply({ $notAnExpression: 5 }, 10);
Type Errors
defaultExpressionEngine.apply({ $get: 123 }, { name: "Chen" });
defaultExpressionEngine.apply({ $case: { value: 5, cases: [{ when: "not boolean", then: "result" }] } });
Data Access Errors
defaultExpressionEngine.apply({ $prop: "name" }, null);
defaultExpressionEngine.evaluate({ $get: { path: "name" } });
Best Practices
- Use
$get with defaults for safe property access: { $get: { path: "name", default: "Unknown" } }
- Use
$literal for values that might be confused with expressions
- Test expressions with sample data before using in production
- Use
$debug to inspect intermediate values in complex pipelines
TypeScript Support
JSON Expressions includes comprehensive TypeScript definitions for type safety and better developer experience.
Basic Usage
import { defaultExpressionEngine, Expression } from "json-expressions";
const expression: Expression = { $gt: 18 };
const result: unknown = defaultExpressionEngine.apply(expression, 25);
if (defaultExpressionEngine.isExpression(someValue)) {
const result = defaultExpressionEngine.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 } }
]
};
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.
- $debug - Logs a value to console and returns it (useful for debugging pipelines)
- $filter - Filters array items based on a condition
- $get - Retrieves a value from data using dot notation paths with optional defaults
- $if - Conditional expression that evaluates different branches based on a condition
- $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
- $pipe - Pipes data through multiple expressions in sequence (left-to-right)
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 } from "json-expressions";
import { math } from "json-expressions/packs/math";
import { string } from "json-expressions/packs/string";
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
- $median - Median (middle value) 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
- $distinct - Returns unique values from an array
- $find - Returns first element that satisfies a predicate
- $flatMap - Maps and flattens array items
- $join - Joins array elements into a string with a separator
- $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
Comparison Pack
Scalar comparison operations for filtering and validation:
- $between - Tests if value is between two bounds (inclusive)
- $in - Tests if value exists in an array
- $isNotNull - Tests if value is not null or undefined
- $isNull - Tests if value is null or undefined
- $nin - Tests if value does not exist in an array
Logic Pack
Boolean logic and conditional operations:
- $and - Logical AND - all expressions must be truthy
- $case - Conditional expression using boolean predicates for complex logic
- $not - Logical NOT - inverts the truthiness of an expression
- $or - Logical OR - at least one expression must be truthy
- $switch - Switch-like expression for deep equality matching
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
- $matchesGlob - Tests if string matches a Unix shell GLOB pattern
- $matchesLike - Tests if string matches a SQL LIKE pattern
- $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
Time Pack
Temporal functions for date/time operations:
- $formatTime - Formats a timestamp using a format string
- $nowLocal - Current date/time as local RFC3339 string with timezone
- $nowUTC - Current date/time as UTC RFC3339 string
- $timeAdd - Adds a duration to a timestamp
- $timeDiff - Calculates difference between two timestamps
- $timestamp - Current timestamp as milliseconds since Unix epoch
Usage Examples
Basic Data Transformation
import { defaultExpressionEngine } from "json-expressions";
const daycareData = {
teacher: { name: "Amara", age: 28 },
children: [
{ name: "Chen", age: 4, activity: "playing" },
{ name: "Fatima", age: 5, activity: "reading" },
{ name: "Diego", age: 3, activity: "napping" }
]
};
const teacherName = defaultExpressionEngine.apply(
{ $get: { path: "name", default: "Unknown" } },
daycareData.teacher
);
const kindergartenReady = defaultExpressionEngine.apply(
{
$pipe: [
{ $get: "children" },
{ $filter: { $gte: 5 } },
{ $map: { $get: "name" } }
]
},
daycareData
);
Static Calculations
const totalMealCost = defaultExpressionEngine.evaluate({
$sum: [8.50, 12.75, 4.25]
});
const sessionId = defaultExpressionEngine.evaluate({ $uuid: null });
Complex Business Logic
const activityRecommendation = defaultExpressionEngine.apply(
{
$switch: {
value: { $get: "age" },
cases: [
{ when: 2, then: "Sensory play and simple puzzles" },
{ when: 3, then: "Art activities and story time" },
{ when: 4, then: "Pre-writing skills and group games" },
{ when: 5, then: "Early math and reading readiness" }
],
default: "Age-appropriate developmental activities"
}
},
{ age: 4 }
);
Custom Expressions
You can easily extend JSON Expressions with custom functionality:
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" });
Installation
npm install json-expressions
License
MIT
Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.