@stackone/expressions v0.13.0
@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 install
Available 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:fix
API 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
true
if the expression is valid - Returns
false
otherwise
isValidExpression("$.user.name"); // Returns true
isValidExpression("invalid $$$ expression"); // Returns false
safeEvaluate(expression: string, context?: object)
Safely evaluates the expression without throwing errors.
- Returns the evaluated result if successful
- Returns
null
if evaluation fails or the expression is invalid
safeEvaluate("$.user.name", { user: { name: "John" } }); // Returns "John"
safeEvaluate("$ invalid expression", {}); // Returns null
Expression 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 |
[start:end:step] | Array slice operator |
?(expression) | Filter expression |
() | Script expression |
Examples:
// Given the context: { user: { name: "John", age: 30 }, "info/email": "info@email.com" }
'$.user.name' // Returns "John"
'$.user.age' // Returns 30
'$.user[*]' // Returns ["John", 30]
'$["info/email"]' // Returns "info@email.com"
For more information on JSON Path syntax, refer to the original JSON Path documentation.
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 10
Identifiers
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 10
hasPassed(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, 2025
now()
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 true
includesSome(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 true
missing(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 false
keys(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)}}"
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"