JSON Expressions
A 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.
Well-suited for configuration-driven applications, business rules engines, and anywhere you need dynamic logic represented as data.
Quick Start
npm install json-expressions
import {
createExpressionEngine,
mathPack,
comparisonPack,
} from "json-expressions";
const engine = createExpressionEngine({ packs: [mathPack, comparisonPack] });
const user = { age: 25, score: 85 };
engine.apply({ $gt: 18 }, user.age);
engine.apply(
{
$matchesAll: {
age: { $gte: 18 },
score: { $gt: 80 },
},
},
user,
);
What Are JSON Expressions?
JSON Expressions let you write logic as JSON that operates on input data. Each expression has a key starting with $ and describes what operation to perform:
{
$gt: 18;
}
{
$get: "name";
}
{
$filterBy: {
age: {
$gte: 18;
}
}
}
Extensible by Design
Create custom expressions for your domain:
const engine = createExpressionEngine({
custom: {
$isValidEmail: (operand, inputData) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(inputData),
},
});
engine.apply({ $isValidEmail: null }, "user@example.com");
Read more about creating custom expressions.
Bundle Size & Tree-Shaking
JSON Expressions is optimized for tree-shaking when using ESM imports. Import only the packs you need for optimal bundle sizes:
| Math pack only | 31 KB | 7 KB |
| Math + Array packs | 36 KB | 8 KB |
| All packs except temporal | 42 KB | 9 KB |
| With temporal (date/time) | 133 KB | 23 KB |
How it works:
import { mathPack, createExpressionEngine } from "json-expressions";
const engine = createExpressionEngine({ packs: [mathPack] });
import {
$add,
$multiply,
$sum,
createExpressionEngine,
} from "json-expressions";
const customPack = { $add, $multiply, $sum };
const engine = createExpressionEngine({ packs: [customPack] });
Requirements:
- Modern bundler (Webpack 5+, Vite, Rollup, esbuild)
- ESM imports (
import, not require)
Node.js with require(): Uses pre-built bundle (~71 KB with external dependencies). For smaller server bundles, use ESM imports in Node 16+.
Design Philosophy
JSON Expressions prioritizes flexibility over raw performance. This library is designed for scenarios where dynamic, data-driven logic is more valuable than execution speed. Performance is an important design goal to enable as many uses as possible, but it will never be a replacement for performance tuned code.
Why JSON Expressions?
Appropriate for:
- Configuration-driven applications - Store complex logic as data in databases
- Business rules that change frequently - Avoid code deployments for logic updates
- Multi-tenant SaaS applications - Different customers need different business logic
- Cross-platform logic sharing - Same rules run in frontend and backend
When NOT to Use JSON Expressions
- High-frequency operations (more than 100k ops/sec requirements)
- Real-time systems with strict latency requirements
- Simple static logic that doesn't need to change
- Performance-critical hot paths
Features:
- Serializable Logic: Express complex computations as JSON that can be stored and transmitted
- Safe Evaluation: Controlled execution environment without the risks of
eval()
- Composable: Build complex logic by combining simple expressions
- Extensible: Add custom expressions through packs and custom definitions
Limitations:
- Performance and Memory Overhead: Interpretation layer adds execution cost compared to native JavaScript
- Synchronous Evaluation: Expressions return values immediately and cannot perform async operations like API calls or database queries
Data Handling
JSON Expressions distinguishes between two types of errors:
- Configuration errors (malformed expressions) - These throw errors immediately
- Data variance (null, undefined, or unexpected types) - These return sensible defaults
This design handles real-world heterogeneous data gracefully:
engine.apply({ $uppercase: null }, null);
engine.apply({ $uppercase: null }, 123);
engine.apply({ $filterBy: { age: { $gt: 5 } } }, null);
engine.apply({ $keys: null }, null);
engine.apply({ $matchesRegex: "\\d+" }, null);
engine.apply({ $exists: "field" }, "not an object");
In databases and APIs, fields often have nullable types (string | null). Expressions can operate on this data without requiring null checks at every step, making them more composable and easier to use in query builders and data pipelines.
Null and undefined are treated identically throughout the library, following JSON semantics where both represent the absence of a value.
Common Patterns
Data Access and Transformation
const order = {
items: [{ price: 10 }, { price: 15 }],
tax: 0.08,
};
engine.apply({ $get: "items" }, order);
engine.apply(
{
$pipe: [{ $get: "items" }, { $map: { $get: "price" } }, { $sum: null }],
},
order,
);
Conditional Logic
const user = { status: "premium", age: 25 };
engine.apply(
{
$if: {
if: { $eq: [{ $get: "status" }, "premium"] },
then: "VIP Access",
else: "Standard Access",
},
},
user,
);
engine.apply(
{
$case: {
value: { $get: "status" },
cases: [
{ when: "premium", then: "Gold Badge" },
{ when: "standard", then: "Silver Badge" },
],
default: "No Badge",
},
},
user,
);
Filtering and Validation
const children = [
{ name: "Ximena", age: 4, active: true },
{ name: "Yousef", age: 5, active: false },
{ name: "Zoë", age: 6, active: true },
];
engine.apply(
{
$filter: {
$and: [{ $get: "active" }, { $gte: [{ $get: "age" }, 5] }],
},
},
children,
);
Validating Expressions
Before evaluating expressions, validate them to catch invalid operators:
const engine = createExpressionEngine();
const errors = engine.validateExpression({ $get: "name" });
if (errors.length === 0) {
}
const errors = engine.validateExpression({
$pipe: [{ $bad1: "oops" }, { $bad2: "another" }],
});
try {
engine.ensureValidExpression({ $typo: "bad" });
} catch (err) {
console.log(err.message);
}
Both methods perform deep validation of nested expressions and correctly handle $literal operands.
Available Expression Packs
Import only the functionality you need:
import {
createExpressionEngine,
mathPack,
comparisonPack,
arrayPack,
objectPack,
stringPack,
filteringPack,
projectionPack,
aggregationPack,
temporalPack,
} from "json-expressions";
const engine = createExpressionEngine({
packs: [mathPack, comparisonPack, arrayPack],
});
→ Complete Pack Reference - Detailed guide to all packs and their expressions
Documentation
Performance
JSON Expressions is optimized for flexibility over raw execution speed. Expect:
- Development speed gains from eliminating deployment cycles
- Good performance for business rules, data transformations, and configuration logic
- Execution overhead compared to native JavaScript functions
- Consider caching for frequently-used complex expressions
Benchmark Results
Performance varies based on operation complexity:
| Simple operations | 1-1.6M ops/sec | $gt, $get, $add |
| Complex operations | 300-700K ops/sec | $matchesAll, $filter |
| Nested data access | 2.4M ops/sec | $get: "user.profile.name" |
| Data processing | 70-300K ops/sec | Pipeline transformations |
| Large datasets (1000 items) | 1.6-3.9K ops/sec | Filtering, grouping |
Run npm run bench for detailed performance metrics on your hardware.
Well suited for configuration logic, business rules, and data processing. Consider direct JavaScript functions for performance-critical hot paths.