@stackone/expressions
v0.21.0
Published
This package can be used to parse and evaluate string expressions with support for variables replacement, functions and operators.
Downloads
5,913
Keywords
Readme
@stackone/expressions
Description
This package can be used to parse and evaluate string expressions with support for variables replacement, functions and operators.
Requirements
Node.js 20+ is required to run this project.
Installation
# install dependencies
$ npm run installAvailable commands
# clean build output
$ npm run clean# build package
$ npm run build# run tests
$ npm run test# run tests on watch mode
$ npm run test:watch# run linter
$ npm run lint# run linter and try to fix any error
$ npm run lint:fixAPI Reference
evaluate(expression: string, context?: object, options?: { incrementalJsonPath?: boolean })
Evaluates the given expression using the provided context.
- Returns the evaluated result
- Throws an error if the expression is invalid or evaluation fails
evaluate("$.user.name", { user: { name: "John" } }); // Returns "John"
evaluate("x + y", { x: 1, y: 2 }); // Returns 3- Setting incrementalJsonPath iterates through any JSON Path expression to check for failure, starting at the root.
- This exchanges performance for better error messages, useful for AI validation and self-repair.
evaluate(
"$.user.name",
{ user: { name: "John" } },
{ incrementalJsonPath: true }
); // Returns "John"
evaluate(
"$.user.details.age",
{ user: { details: { name: "John" } } },
{ incrementalJsonPath: true }
); // Throws Error: "Key 'age' not found at '$.user.details'. Available keys: name"isValidExpression(expression: string)
Checks if the given expression is valid.
- Returns
trueif the expression is valid - Returns
falseotherwise
isValidExpression("$.user.name"); // Returns true
isValidExpression("invalid $$$ expression"); // Returns falsesafeEvaluate(expression: string, context?: object)
Safely evaluates the expression without throwing errors.
- Returns the evaluated result if successful
- Returns
nullif evaluation fails or the expression is invalid
safeEvaluate("$.user.name", { user: { name: "John" } }); // Returns "John"
safeEvaluate("$ invalid expression", {}); // Returns nullExpression language syntax
There are three types of expressions supported:
JSON Path Expressions
When the expression starts with $, it is treated as a JSON Path expression and will be evaluated as such.
JSON Path Syntax
| JSON Path | Description |
| ------------------ | ------------------------------------------------------------------ |
| $ | The root object |
| . | Child operator |
| @ | The current object |
| * | Wildcard. All elements in an array, or all properties of an object |
| .. | Recursive descent |
| [] | Subscript operator |
| [,] | Union operator (e.g., $.a[b,c] for multiple properties) |
| [start:end:step] | Array slice operator |
| ?(expression) | Filter expression |
| () | Script expression |
Examples:
// Given the context: { user: { name: "John", age: 30 }, "info/email": "[email protected]" }
"$.user.name"; // Returns "John"
"$.user.age"; // Returns 30
"$.user[*]"; // Returns ["John", 30]
"$.user[name,age]"; // Returns ["John", 30] (union operator)
'$["info/email"]'; // Returns "[email protected]"For more information on JSON Path syntax, refer to the JSONPath Plus documentation and the original JSON Path specification.
JEXL Expressions
This kind of expression is enclosed in double brackets {{expression}}. It supports variables and operators.
Operators
| Operator | Description |
| -------- | ------------------------------ |
| ! | Logical NOT |
| + | Addition, string concatenation |
| - | Subtraction |
| * | Multiplication |
| / | Division |
| // | Floor division |
| % | Modulus |
| ^ | Exponentiation |
| && | Logical AND |
| \|\| | Logical OR |
| == | Equal |
| != | Not equal |
| > | Greater than |
| >= | Greater than or equal |
| < | Less than |
| <= | Less than or equal |
| in | Element of string or array |
| ? : | Ternary operator |
| ?? | Nullish coalescing operator |
Examples:
// Given the context: { x: 10, y: 5 }
"{{x + y}}"; // Returns 15
"{{x * 2}}"; // Returns 20
"{{x > y}}"; // Returns true
'{{x == 10 ? "yes" : "no"}}'; // Returns "yes"
"{{x in [1, 2, 3]}}"; // Returns false
"{{x != y}}"; // Returns true
"{{x ?? y}}"; // Returns 10Identifiers
Identifiers can be used to reference variables in the context.
// Given the context:
// {
// name: {
// first: "John",
// last: "Smith"
// },
// jobs: ["Developer", "Designer"]
// }
`{{name.first}}` // Returns "John"
`{{jobs[1]}}`; // Returns "Designer"Collections
Collections, or arrays of objects, can be filtered by including a filter expression in brackets.
// Given the context:
// {
// users: [
// { name: "John", age: 30 },
// { name: "Jane", age: 25 }
// ]
// }
`{{users[.name == "John"].age}}` // Returns 30
`{{users[.age > 25].name}}`; // Returns ["John"]Built-in Functions
The expression handler provides several built-in functions you can use in your expressions:
Date Functions
nextAnniversary(initialDate, format)
Calculates the next anniversary date for a given date.
initialDate(string): The initial date stringformat(string): The format of the date string (uses date-fns format)- Returns: Date object or null
// If today is April 10, 2025, and a birthday is December 25, 2000
"{{nextAnniversary('25/12/2000', 'dd/MM/yyyy')}}";
// Returns Date object for December 25, 2025 (this year's anniversary)
// If today is April 10, 2025, and a birthday is February 15, 1990
"{{nextAnniversary('15/02/1990', 'dd/MM/yyyy')}}";
// Returns Date object for February 15, 2026 (next year's anniversary)yearsElapsed(startDate, format, endDate?)
Calculates the number of complete years elapsed between two dates.
startDate(string): The start date stringformat(string): The format of the date string (uses date-fns format)endDate(string, optional): The end date string, defaults to current date if omitted- Returns: number of years or null
// Calculate years between two specific dates
"{{yearsElapsed('01/01/2015', 'dd/MM/yyyy', '01/01/2025')}}"; // Returns 10
// Calculate years from a date to today (assuming today is April 10, 2025)
"{{yearsElapsed('01/01/2015', 'dd/MM/yyyy')}}"; // Returns 10hasPassed(date, format, yearsToAdd?)
Determines if a given date (optionally with added years) has passed.
date(string): The date to checkformat(string): The format of the date string (uses date-fns format)yearsToAdd(number, optional): Number of years to add to the date before comparing- Returns: boolean
// Check if a date has passed (assuming today is April 10, 2025)
"{{hasPassed('01/01/2020', 'dd/MM/yyyy')}}"; // Returns true
// Check if a date has passed (assuming today is April 10, 2025)
"{{hasPassed('01/01/2026', 'dd/MM/yyyy')}}"; // Returns false
// Check if a date + 5 years has passed (2020 + 5 = 2025)
"{{hasPassed('01/01/2020', 'dd/MM/yyyy', 5)}}";
// Returns true if April 10, 2025 is after January 1, 2025now()
Returns the current date and time.
- Returns: string (ISO 8601 format)
// Get the current date and time
"{{now()}}"; // Returns "2025-04-10T12:00:00.000Z" (example)Array Functions
includes(array, value)
Checks if an array includes a specific value or all values from another array.
array(array): The array to checkvalue(any | array): The value(s) to search for in the array- Returns: boolean
// Check if an array includes a specific value
"{{includes([1, 2, 3], 2)}}"; // Returns true
// Check if an array includes a specific value
"{{includes([1, 2, 3], 5)}}"; // Returns false
// Can be used with context variables
"{{includes($.allowedRoles, $.currentUser.role)}}";
// Check if an array includes all of values of the second array
"{{includes([1, 2, 3, 4], [2, 4])}}"; // Returns trueincludesSome(array, value)
Checks if an array includes at least one value from another array or a single value.
array(array): The array to checkvalue(any | array): The value(s) to search for in the array- Returns: boolean
// Check if an array includes at least one value
"{{includesSome([1, 2, 3], 2)}}"; // Returns true
// Check if an array includes at least one value from another array
"{{includesSome([1, 2, 3], [2, 5])}}"; // Returns true
// Check if an array includes at least one value from another array (none match)
"{{includesSome([1, 2, 3], [4, 5])}}"; // Returns false
// Can be used with context variables
"{{includesSome($.allowedRoles, $.currentUser.roles)}}";Object Functions
present(object)
Checks if an object is present (not null or undefined).
object(object): The object to check- Returns: boolean
// Check if an object is present
"{{present({})}}"; // Returns true
"{{present(null)}}"; // Returns false
"{{present(undefined)}}"; // Returns false
"{{present('string')}}"; // Returns true
"{{present(0)}}"; // Returns true
"{{present([])}}"; // Returns truemissing(object)
Checks if an object is missing (null or undefined).
object(object): The object to check- Returns: boolean
// Check if an object is not present
"{{missing({})}}"; // Returns false
"{{missing(null)}}"; // Returns true
"{{missing(undefined)}}"; // Returns true
"{{missing('string')}}"; // Returns false
"{{missing(0)}}"; // Returns false
"{{missing([])}}"; // Returns falsekeys(object)
Returns the keys of an object as an array. If the input is not an object, null, or undefined, returns an empty array.
object(object): The object to get keys from- Returns: array of keys (string[])
// Get keys from an object
"{{keys({ a: 1, b: 2 })}}"; // Returns ["a", "b"]
// Get keys from a context variable
"{{keys($.user)}}";values(object)
Returns the values of an object as an array. If the input is not an object, null, or undefined, returns an empty array.
object(object): The object to get values from- Returns: array of values (any[])
// Get values from an object
"{{values({ a: 1, b: 2 })}}"; // Returns [1, 2]
// Get values from a context variable
"{{values($.user)}}";String Functions
capitalize(value, mode?)
Capitalizes characters in a string.
value(string): The string to capitalizemode(string, optional): 'first' to capitalize first character only, 'each' to capitalize each word (default: 'first')- Returns: capitalized string, or empty string for invalid input
// Capitalize first character (default)
"{{capitalize('hello')}}"; // Returns "Hello"
"{{capitalize('hello world')}}"; // Returns "Hello world"
// Capitalize each word
"{{capitalize('hello world', 'each')}}"; // Returns "Hello World"
"{{capitalize('the great gatsby', 'each')}}"; // Returns "The Great Gatsby"
// Capitalize from context variable
"{{capitalize($.name)}}";
"{{capitalize($.title, 'each')}}";decodeBase64(encodedValue)
Decodes a Base64 encoded string and returns the decoded result.
encodedValue(string): The Base64 encoded string to decode- Returns: decoded string, or empty string if input is invalid
// Decode a base64 string directly
"{{decodeBase64("SGVsbG8gV29ybGQ")}}" // Returns "Hello World"
// Decode from context variable
"{{decodeBase64($.encodedValue)}}" // Decodes the encodedValue from context
// Handles invalid cases gracefully
"{{decodeBase64(null)}}" // Returns ""
"{{decodeBase64("!")}}" // Returns ""
"{{decodeBase64(123)}}" // Returns ""encodeBase64(value)
Encodes a string to Base64 and returns the encoded result.
value(string): The string to encode- Returns: encoded string, or empty string if input is invalid
// Encode a string directly
"{{encodeBase64("Hello World")}}" // Returns "SGVsbG8gV29ybGQ"
// Encode from context variable
"{{encodeBase64($.value)}}" // Encodes the value from context
// Handles invalid cases gracefully
"{{encodeBase64(null)}}" // Returns ""
"{{encodeBase64(123)}}" // Returns ""
"{{encodeBase64("")}}" // Returns ""truncate(value, maxLength, suffix?)
Truncates a string to a specified maximum length, optionally appending a suffix.
value(string): The string to truncatemaxLength(number): Maximum length of the result (including suffix)suffix(string, optional): Suffix to append when truncating, defaults to "..."- Returns: truncated string, or original string if shorter than maxLength
// Truncate with default suffix
"{{truncate('Hello World', 8)}}"; // Returns "Hello..."
// Truncate with custom suffix
"{{truncate('Hello World', 8, '…')}}"; // Returns "Hello W…"
// Truncate with no suffix
"{{truncate('Hello World', 5, '')}}"; // Returns "Hello"
// No truncation needed
"{{truncate('Hi', 10)}}"; // Returns "Hi"
// Truncate from context variable
"{{truncate($.description, 100)}}";padStart(value, targetLength, padString?)
Pads the start of a string with another string until it reaches the target length.
value(string | number): The value to pad (numbers are converted to strings)targetLength(number): The target length of the resulting stringpadString(string, optional): The string to pad with, defaults to space " "- Returns: padded string, or original if already at or beyond target length
// Pad with zeros (common for formatting numbers)
"{{padStart('5', 3, '0')}}"; // Returns "005"
"{{padStart(5, 3, '0')}}"; // Returns "005" (numbers work too)
// Pad with default space
"{{padStart('hello', 10)}}"; // Returns " hello"
// Pad with custom character
"{{padStart('abc', 6, '*')}}"; // Returns "***abc"
// No padding needed if already long enough
"{{padStart('hello', 3)}}"; // Returns "hello"
// Pad from context variable
"{{padStart($.id, 8, '0')}}";regexMatch(value, pattern, groupIndex?)
Extracts a value from a string using a regular expression pattern. Returns the specified capture group, or null if no match is found.
value(string): The string to search inpattern(string): The regex pattern as a string (without delimiters)groupIndex(number, optional): Capture group index to return (default: 1 for first capture group, 0 for full match)- Returns: matched string from the specified group, or null if no match or group doesn't exist
// Extract parameter from URL or header
"{{regexMatch('<https://api.com?after=abc123&limit=2>; rel=\"next\"', 'after=([^&>]+)', 1)}}"; // Returns "abc123"
// Extract full match (group 0)
"{{regexMatch('Hello World', 'World', 0)}}"; // Returns "World"
// Extract with capture group
"{{regexMatch('user_id=12345', 'user_id=(\\d+)', 1)}}"; // Returns "12345"
// No match returns null
"{{regexMatch('Hello World', 'foo', 1)}}"; // Returns null
// Extract from context variable
"{{regexMatch($.linkHeader, 'after=([^&>]+)', 1)}}";For more information on the JEXL syntax, refer to the JEXL Syntax documentation.
String Interpolation
To simplify strings usage, a more straightforward syntax is provided for string interpolation of variables using the ${var} syntax.
Examples:
// Given the context: { name: "John", age: 30 }
"Hello ${name}"; // Returns "Hello John"
"User is ${age}"; // Returns "User is 30"
// You can also use JEXL inside string syntax
"Status: ${age > 18 ? 'Adult' : 'Minor'}"; // Returns "Status: Adult"
"Age in 5 years: ${age + 5}"; // Returns "Age in 5 years: 35"Note: If the expression is a string without any of the patterns described above, it will be returned as is.
// Given the context: { name: "John", age: 30 }
"Hello world"; // Returns "Hello world"