npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@pawel-up/jexl

v4.4.2

Published

Javascript Expression Language: Powerful context-based expression parser and evaluator

Readme

Jexl

A modern, TypeScript-first fork of the original Jexl library, brought to the XXI century.

Maintained by Pawel Uchida-Psztyc (@jarrodek) with modern tooling, enhanced type safety, and improved developer experience.

Original library created by Tom Shawver.

Javascript Expression Language: Powerful context-based expression parser and evaluator

Why This Fork?

This modernized version of Jexl brings several key improvements over the original:

🚀 Modern TypeScript Support

  • Full TypeScript rewrite with comprehensive type definitions
  • Enhanced type safety with unknown types instead of unsafe any
  • Better IDE support with full IntelliSense and autocomplete
  • Type-safe transform and function definitions - write your custom transforms with proper typing

🛠 Modern Development Experience

  • ESM-first with proper ES module support
  • Modern build tooling with up-to-date dependencies
  • Comprehensive test coverage with modern testing framework
  • Clean, maintainable codebase following current best practices

📦 Developer-Friendly

  • Flexible function signatures - define transforms with specific parameter types
  • Namespace support - organize functions and transforms with dot notation (e.g., Utils.String.upper, Api.User.getName())
  • Better error handling and debugging experience
  • Consistent API that works seamlessly in both Node.js and modern browsers
  • Tree-shakeable for optimal bundle sizes

🔧 Enhanced Type Safety Example

// Before: Unsafe any types
jexl.addTransform('multiply', (val: any, factor: any) => val * factor)

// Now: Type-safe transforms
jexl.addTransform('multiply', (val: number, factor: number) => val * factor)
jexl.addTransform('upperCase', (val: string) => val.toUpperCase())
jexl.addTransform('formatDate', (val: Date, format: string) => /* ... */)

Quick start

Use it with promises or synchronously:

const context = {
  name: { first: 'Sterling', last: 'Archer' },
  assoc: [
    { first: 'Lana', last: 'Kane' },
    { first: 'Cyril', last: 'Figgis' },
    { first: 'Pam', last: 'Poovey' }
  ],
  age: 36
}

// Filter an array asynchronously...
await const res = jexl.eval('assoc[.first == "Lana"].last', context)
console.log(res) // Output: Kane

// Do math
await jexl.eval('age * (3 - 1)', context)
// 72

// Concatenate
await jexl.eval('name.first + " " + name["la" + "st"]', context)
// "Sterling Archer"

// Compound
await jexl.eval(
  'assoc[.last == "Figgis"].first == "Cyril" && assoc[.last == "Poovey"].first == "Pam"',
  context
)
// true

// Use array indexes
await jexl.eval('assoc[1]', context)
// { first: 'Cyril', last: 'Figgis' }

// Use conditional logic
await jexl.eval('age > 62 ? "retired" : "working"', context)
// "working"

// Transform
jexl.addTransform('upper', (val) => val.toUpperCase())
await jexl.eval('"duchess"|upper + " " + name.last|upper', context)
// "DUCHESS ARCHER"

// Namespace transforms (NEW!)
jexl.addTransform('String.upper', (val) => val.toUpperCase())
jexl.addTransform('String.repeat', (val, times) => val.repeat(times))
await jexl.eval('"hi"|String.repeat(3)|String.upper', context)
// "HIHIHI"

// Transform asynchronously, with arguments
jexl.addTransform('getStat', async (val, stat) => dbSelectByLastName(val, stat))
try {
  const res = await jexl.eval('name.last|getStat("weight")', context)
  console.log(res) // Output: 184
} catch (e) {
  console.log('Database Error', e.stack)
}

// Functions too, sync or async, args or no args
jexl.addFunction('getOldestAgent', () => db.getOldestAgent())
await jexl.eval('age == getOldestAgent().age', context)
// false

// Namespace functions (NEW!)
jexl.addFunction('Math.max', (a, b) => Math.max(a, b))
jexl.addFunction('Utils.String.slugify', (text) =>
  text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''))
await jexl.eval('Math.max(age, 25)', context)
// 36

// Add your own (a)synchronous operators
// Here's a case-insensitive string equality
jexl.addBinaryOp(
  '_=',
  20,
  (left, right) => left.toLowerCase() === right.toLowerCase()
)
await jexl.eval('"Guest" _= "gUeSt"')
// true

// Compile your expression once, evaluate many times!
const { expr } = jexl
const danger = expr`"Danger " + place` // Also: jexl.compile('"Danger " + place')
await danger.eval({ place: 'zone' }) // Danger zone
await danger.eval({ place: 'ZONE!!!' }) // Danger ZONE!!! (Doesn't recompile the expression!)

Migration from Original Jexl

Upgrading from the original Jexl library is straightforward:

Package Installation

# Remove old package
npm uninstall jexl

# Install modern version
npm install @pawel-up/jexl

Import Changes

// Before
const jexl = require('jexl')
// or
import jexl from 'jexl'

// Now
import { Jexl } from '@pawel-up/jexl'

Enhanced Transform and Function Definitions

The API remains the same, but you can now use proper TypeScript types and namespace organization:

// Before: No type safety
jexl.addTransform('multiply', (val, factor) => val * factor)

// Now: Full type safety (optional but recommended)
jexl.addTransform('multiply', (val: number, factor: number): number => val * factor)

// NEW: Namespace support for better organization
jexl.addTransform('Math.multiply', (val: number, factor: number): number => val * factor)
jexl.addTransform('String.upper', (val: string): string => val.toUpperCase())
jexl.addFunction('Utils.String.slugify', (text: string): string =>
  text
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
)

Backwards Compatibility

  • 99% API compatible - existing code mostly works without changes. We removed the evalSync() methods.
  • Same expression syntax - no need to update your expressions
  • Same behavior - results are identical to the original library
  • Same performance - optimized for modern JavaScript engines

Installation

Jexl works on the backend, and on the frontend if bundled using a bundler like Parcel or Webpack.

Install from npm:

npm install @pawel-up/jexl --save

and use it:

import { Jexl } from '@pawel-up/jexl'

All the details

Unary Operators

| Operation | Symbol | | --------- | :----: | | Negate | ! |

Binary Operators

| Operation | Symbol | | ---------------- | :----------: | | Add, Concat | + | | Subtract | - | | Multiply | * | | Divide | / | | Divide and floor | // | | Modulus | % | | Power of | ^ | | Logical AND | && | | Logical OR | || |

Comparisons

| Comparison | Symbol | | -------------------------- | :----: | | Equal | == | | Not equal | != | | Greater than | > | | Greater than or equal | >= | | Less than | < | | Less than or equal | <= | | Element in array or string | in |

A note about in

The in operator can be used to check for a substring: "Cad" in "Ron Cadillac", and it can be used to check for an array element: "coarse" in ['fine', 'medium', 'coarse']. However, the == operator is used behind-the-scenes to search arrays, so it should not be used with arrays of objects. The following expression returns false: {a: 'b'} in [{a: 'b'}].

Ternary operator

Conditional expressions check to see if the first segment evaluates to a truthy value. If so, the consequent segment is evaluated. Otherwise, the alternate is. If the consequent section is missing, the test result itself will be used instead.

| Expression | Result | | --------------------------------- | ------ | | "" ? "Full" : "Empty" | Empty | | "foo" in "foobar" ? "Yes" : "No" | Yes | | {agent: "Archer"}.agent ?: "Kane" | Archer |

Native Types

| Type | Examples | | -------- | :----------------------------: | | Booleans | true, false | | Strings | "Hello "user"", 'Hey there!' | | Numerics | 6, -7.2, 5, -3.14159 | | Objects | {hello: "world!"} | | Arrays | ['hello', 'world!'] |

Groups

Parentheses work just how you'd expect them to:

| Expression | Result | | ----------------------------------- | :----- | | (83 + 1) / 2 | 42 | | 1 < 3 && (4 > 2 || 2 > 4) | true |

Identifiers

Access variables in the context object by just typing their name. Objects can be traversed with dot notation, or by using brackets to traverse to a dynamic property name.

Example context:

{
  name: {
    first: "Malory",
    last: "Archer"
  },
  exes: [
    "Nikolai Jakov",
    "Len Trexler",
    "Burt Reynolds"
  ],
  lastEx: 2
}

| Expression | Result | | ----------------- | ------------- | | name.first | Malory | | name['la' + 'st'] | Archer | | exes[2] | Burt Reynolds | | exes[lastEx - 1] | Len Trexler |

Collections

Collections, or arrays of objects, can be filtered by including a filter expression in brackets. Properties of each collection can be referenced by prefixing them with a leading dot. The result will be an array of the objects for which the filter expression resulted in a truthy value.

Example context:

{
    employees: [
        {first: 'Sterling', last: 'Archer', age: 36},
        {first: 'Malory', last: 'Archer', age: 75},
        {first: 'Lana', last: 'Kane', age: 33},
        {first: 'Cyril', last: 'Figgis', age: 45},
        {first: 'Cheryl', last: 'Tunt', age: 28}
    ],
    retireAge: 62
}

| Expression | Result | | --------------------------------------------- | ------------------------------------------------------------------------------------- | | employees[.first == 'Sterling'] | [{first: 'Sterling', last: 'Archer', age: 36}] | | employees[.last == 'Tu' + 'nt'].first | Cheryl | | employees[.age >= 30 && .age < 40] | [{first: 'Sterling', last: 'Archer', age: 36},{first: 'Lana', last: 'Kane', age: 33}] | | employees[.age >= 30 && .age < 40][.age < 35] | [{first: 'Lana', last: 'Kane', age: 33}] | | employees[.age >= retireAge].first | Malory |

Transforms

The power of Jexl is in transforming data, synchronously or asynchronously. Transform functions take one or more arguments: The value to be transformed, followed by anything else passed to it in the expression. They must return either the transformed value, or a Promise that resolves with the transformed value. Add them with jexl.addTransform(name, function).

jexl.addTransform('split', (val, char) => val.split(char))
jexl.addTransform('lower', (val) => val.toLowerCase())

| Expression | Result | | ---------------------------------------- | --------------------- | | "Pam Poovey" \| lower \| split[' '](1) | poovey | | "password==guest" \| split('=' + '=') | ['password', 'guest'] |

Advanced Transforms

Using Transforms, Jexl can support additional string formats like embedded JSON, YAML, XML, and more. The following, with the help of the xml2json module, allows XML to be traversed just as easily as plain javascript objects:

const xml2json = require('xml2json')

jexl.addTransform('xml', (val) => xml2json.toJson(val, { object: true }))

const context = {
  xmlDoc: `
    <Employees>
      <Employee>
        <FirstName>Cheryl</FirstName>
        <LastName>Tunt</LastName>
      </Employee>
      <Employee>
        <FirstName>Cyril</FirstName>
        <LastName>Figgis</LastName>
      </Employee>
    </Employees>`,
}

var expr = 'xmlDoc|xml.Employees.Employee[.LastName == "Figgis"].FirstName'

jexl.eval(expr, context).then(console.log) // Output: Cyril

Namespace Functions and Transforms

Jexl supports organizing functions and transforms into namespaces using dot notation. This allows you to create hierarchical structures and avoid naming conflicts in large applications.

Namespace Functions

Functions can be organized into namespaces by using dot-separated names when registering them:

// Register namespace functions
jexl.addFunction('Math.max', (a, b) => Math.max(a, b))
jexl.addFunction('Utils.String.slugify', (text) =>
  text
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
)
jexl.addFunction('Api.User.getName', async (userId) => {
  const user = await fetchUser(userId)
  return user.name
})

// Use namespace functions in expressions
await jexl.eval('Math.max(10, 25)') // 35
await jexl.eval('Utils.String.slugify("Hello World!")') // "hello-world"
await jexl.eval('Api.User.getName(123)') // "John Doe"

Namespace Transforms

Transforms can also be organized into namespaces and used with the pipe operator:

// Register namespace transforms
jexl.addTransform('String.upper', (val) => val.toUpperCase())
jexl.addTransform('String.lower', (val) => val.toLowerCase())
jexl.addTransform('String.repeat', (val, times) => val.repeat(times))
jexl.addTransform('Utils.Text.capitalize', (text) => text.charAt(0).toUpperCase() + text.slice(1).toLowerCase())
jexl.addTransform('Format.Date.iso', (date) => new Date(date).toISOString())

// Use namespace transforms in expressions
await jexl.eval('"hello world"|String.upper') // "HELLO WORLD"
await jexl.eval('"HELLO WORLD"|String.lower') // "hello world"
await jexl.eval('"hi"|String.repeat(3)') // "hihihi"
await jexl.eval('"hello world"|Utils.Text.capitalize') // "Hello world"
await jexl.eval('"2024-01-01"|Format.Date.iso') // "2024-01-01T00:00:00.000Z"

Deeply Nested Namespaces

Namespaces can be nested to any depth:

// Deep namespace registration
jexl.addFunction('Company.Departments.HR.getEmployeeCount', () => 150)
jexl.addTransform('Data.Validation.Email.isValid', (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))

// Usage in expressions
await jexl.eval('Company.Departments.HR.getEmployeeCount()') // 150
await jexl.eval('"[email protected]"|Data.Validation.Email.isValid') // true

Chaining Namespace Transforms

Namespace transforms can be chained just like regular transforms:

// Register multiple namespace transforms
jexl.addTransform('String.trim', (val) => val.trim())
jexl.addTransform('String.upper', (val) => val.toUpperCase())
jexl.addTransform('String.split', (val, separator) => val.split(separator))

// Chain namespace transforms
await jexl.eval('"  hello world  "|String.trim|String.upper|String.split(" ")')
// Result: ["HELLO", "WORLD"]

Namespace Transforms with Arguments

Namespace transforms support arguments just like regular transforms:

// Transforms with multiple arguments
jexl.addTransform('String.padStart', (val, length, padString) => val.padStart(length, padString))
jexl.addTransform('Array.slice', (arr, start, end) => arr.slice(start, end))

// Usage with arguments
await jexl.eval('"5"|String.padStart(3, "0")') // "005"
await jexl.eval('[1,2,3,4,5]|Array.slice(1, 4)') // [2,3,4]

Mixed Namespace and Regular Functions/Transforms

You can mix namespace and regular functions/transforms in the same expression:

// Register both types
jexl.addFunction('min', Math.min)
jexl.addFunction('Utils.Math.average', (arr) => arr.reduce((a, b) => a + b) / arr.length)
jexl.addTransform('sort', (arr) => [...arr].sort())
jexl.addTransform('Array.reverse', (arr) => [...arr].reverse())

// Mix in expressions
await jexl.eval('min(Utils.Math.average([1,2,3]), 5)') // 2
await jexl.eval('[3,1,4,2]|sort|Array.reverse') // [4,3,2,1]

Namespace Organization Best Practices

Consider organizing your functions and transforms by domain or functionality:

// By domain
jexl.addFunction('User.authenticate', authenticateUser)
jexl.addFunction('User.authorize', authorizeUser)
jexl.addFunction('Product.getPrice', getProductPrice)
jexl.addFunction('Order.calculate', calculateOrder)

// By data type
jexl.addTransform('String.capitalize', capitalizeString)
jexl.addTransform('String.truncate', truncateString)
jexl.addTransform('Array.unique', uniqueArray)
jexl.addTransform('Array.flatten', flattenArray)
jexl.addTransform('Date.format', formatDate)
jexl.addTransform('Date.addDays', addDaysToDate)

// By utility category
jexl.addFunction('Validation.isEmail', isValidEmail)
jexl.addFunction('Validation.isPhoneNumber', isValidPhoneNumber)
jexl.addTransform('Format.currency', formatCurrency)
jexl.addTransform('Format.percentage', formatPercentage)

Migration from Non-Namespaced Functions/Transforms

Existing non-namespaced functions and transforms continue to work unchanged:

// Existing code continues to work
jexl.addFunction('myFunc', () => 'old way')
jexl.addTransform('myTransform', (val) => val + ' transformed')

await jexl.eval('myFunc()') // "old way"
await jexl.eval('"test"|myTransform') // "test transformed"

// New namespace versions can coexist
jexl.addFunction('Utils.myFunc', () => 'new way')
jexl.addTransform('Utils.myTransform', (val) => val + ' namespace transformed')

await jexl.eval('Utils.myFunc()') // "new way"
await jexl.eval('"test"|Utils.myTransform') // "test namespace transformed"

Functions

While Transforms are the preferred way to change one value into another value, Jexl also allows top-level expression functions to be defined. Use these to provide access to functions that either don't require an input, or require multiple equally-important inputs. They can be added with jexl.addFunction(name, function). Like transforms, functions can return a value, or a Promise that resolves to the resulting value.

jexl.addFunction('min', Math.min)
jexl.addFunction('expensiveQuery', async () => db.runExpensiveQuery())

| Expression | Result | | --------------------------------------------- | ------------------------- | | min(4, 2, 19) | 2 | | counts.missions || expensiveQuery() | Query only runs if needed |

Context

Variable contexts are straightforward Javascript objects that can be accessed in the expression, but they have a hidden feature: they can include a Promise object, and when that property is used, Jexl will wait for the Promise to resolve and use that value!

API Reference

Jexl Instance

jexl.Jexl

A reference to the Jexl constructor. To maintain separate instances of Jexl with each maintaining its own set of transforms, simply re-instantiate with new jexl.Jexl().

jexl.addBinaryOp({string} operator, {number} precedence, {function} fn, {boolean} [manualEval])

Adds a binary operator to the Jexl instance. A binary operator is one that considers the values on both its left and right, such as "+" or "==", in order to calculate a result. The precedence determines the operator's position in the order of operations (please refer to lib/grammar.js to see the precedence of existing operators). The provided function will be called with two arguments: a left value and a right value. It should return either the resulting value, or a Promise that resolves to the resulting value.

If manualEval is true, the left and right arguments will be wrapped in objects with an eval function. Calling left.eval() or right.eval() will return a promise that resolves to that operand's actual value. This is useful to conditionally evaluate operands, and is how && and || work.

jexl.addUnaryOp({string} operator, {function} fn)

Adds a unary operator to the Jexl instance. A unary operator is one that considers only the value on its right, such as "!", in order to calculate a result. The provided function will be called with one argument: the value to the operator's right. It should return either the resulting value, or a Promise that resolves to the resulting value.

jexl.addFunction({string} name, _{function} func)

Adds an expression function to this Jexl instance. The name can be a simple identifier (e.g., 'myFunction') or a namespace path using dot notation (e.g., 'Utils.String.slugify'). See the Functions and Namespace Functions and Transforms sections above for information on the structure of an expression function.

jexl.addFunctions({{}} map)

Adds multiple functions from a supplied map of function name to expression function.

jexl.addTransform({string} name, {function} transform)

Adds a transform function to this Jexl instance. The name can be a simple identifier (e.g., 'myTransform') or a namespace path using dot notation (e.g., 'String.upper'). See the Transforms and Namespace Functions and Transforms sections above for information on the structure of a transform function.

jexl.addTransforms({{}} map)

Adds multiple transforms from a supplied map of transform name to transform function.

jexl.compile({string} expression)

Constructs an Expression object around the given Jexl expression string. Expression objects allow a Jexl expression to be compiled only once but evaluated many times. See the Expression API below. Note that the only difference between this function and jexl.createExpression is that this function will immediately compile the expression, and throw any errors associated with invalid expression syntax.

jexl.createExpression({string} expression)

Constructs an Expression object around the given Jexl expression string. Expression objects allow a Jexl expression to be compiled only once but evaluated many times. See the Expression API below.

jexl.getTransform({string} name)

Returns {function|undefined}. Gets a previously set transform function, or undefined if no function of that name exists.

jexl.eval({string} expression, {{}} [context])

Returns {Promise<*>}. Evaluates an expression. The context map is optional.

jexl.expr: tagged template literal

A convenient bit of syntactic sugar for jexl.createExpression

const someNumber = 10
const expression = jexl.expr`5 + ${someNumber}`
console.log(await expression.eval()) // 15

Note that expr will stay bound to its associated Jexl instance even if it's pulled out of context:

const { expr } = jexl
jexl.addTransform('double', (val) => val * 2)
const expression = expr`2|double`
console.log(await expression.eval()) // 4

jexl.removeOp({string} operator)

Removes a binary or unary operator from the Jexl instance. For example, "^" can be passed to eliminate the "power of" operator.

Expression

Expression objects are created via jexl.createExpression, jexl.compile, or jexl.expr, and are a convenient way to ensure jexl expressions compile only once, even if they're evaluated multiple times.

expression.compile()

Returns self {Expression}. Forces the expression to compile, even if it was compiled before. Note that each compile will happen with the latest grammar and transforms from the associated Jexl instance.

expression.eval({{}} [context])

Returns {Promise<*>}. Evaluates the expression. The context map is optional.

Expression Validation

This modernized version of Jexl includes a powerful Validator class that helps you validate expressions before evaluation, providing comprehensive error reporting and analysis.

Validator Class

The Validator class provides static methods to validate Jexl expressions, offering detailed feedback about syntax errors, semantic issues, and potential problems.

Key Features

  • 🔍 Comprehensive Validation: Checks syntax, semantics, and context usage
  • 🚨 Detailed Error Reporting: Provides specific error messages with position information
  • ⚠️ Warning System: Identifies potential issues and performance concerns
  • ℹ️ Informational Messages: Offers suggestions for improvements
  • 🧹 Automatic Whitespace Trimming: Cleans expressions before validation
  • 🔧 Context-Agnostic Mode: Validates expressions without requiring specific context
  • JavaScript-aligned Logic: Correctly distinguishes between null and undefined and handles &&/|| operator precedence just like in JS.
  • Powerful Operators: Full support for unary minus and plus on expressions (e.g., -myFunc(), +variable).
  • Robust Parsing: Handles complex cases like negative numbers in function arguments (myFunc(5, -3)), ternary operators after function calls, and leading decimal numbers (.5).
  • Clear Error Handling: Provides precise errors for ambiguous syntax like 2++2.

Basic Usage

import { Validator, Jexl } from '@pawel-up/jexl'

const jexl = new Jexl()
const validator = new Validator(jexl.grammar)

// Validate a simple expression
const result = validator.validate('name.first + " " + name.last')
console.log(result.isValid) // true
console.log(result.errors) // []

// Validate with context
const context = { name: { first: 'John', last: 'Doe' } }
const result2 = validator.validate('name.middle', context)
console.log(result2.isValid) // false
console.log(result2.errors[0].message) // "Property 'middle' not found in context object 'name'"

Validation Result

The validate method returns a ValidationResult object with the following properties:

interface ValidationResult {
  isValid: boolean // Overall validation status
  errors: ValidationIssue[] // Critical errors that prevent execution
  warnings: ValidationIssue[] // Non-critical issues that might cause problems
  info: ValidationIssue[] // Informational suggestions
  trimmedExpression: string // Expression after automatic whitespace trimming
}

interface ValidationIssue {
  severity: 'error' | 'warning' | 'info'
  message: string
  position?: number // Character position in expression
  line?: number // Line number (for multi-line expressions)
  column?: number // Column number
}

Automatic Whitespace Trimming

The Validator automatically trims leading and trailing whitespace from expressions before validation:

// These expressions are equivalent after trimming
validator.validate('name.first')
validator.validate('   name.first   ')
validator.validate('\t\nname.first\n\t')

Context-Agnostic Validation

Use allowUndefinedContext: true to validate expressions without providing specific context:

// Validate syntax only, ignore missing context
const result = validator.validate('user.profile.email', undefined, {
  allowUndefinedContext: true,
})

console.log(result.isValid) // true (if syntax is correct)
console.log(result.warnings) // May contain warnings about undefined context

Validation Examples

// Syntax error
const syntaxError = validator.validate('name.first +')
console.log(syntaxError.errors[0].message) // "Unexpected end of expression"

// Context validation
const context = { user: { name: 'John' } }
const contextError = validator.validate('user.age', context)
console.log(contextError.errors[0].message) // "Property 'age' not found in context object 'user'"

// Performance warning
const perfWarning = validator.validate('users[.name == "John" && .active == true]')
console.log(perfWarning.warnings[0].message) // "Complex filter expression may impact performance"

// Style suggestion
const styleInfo = validator.validate('name["first"]')
console.log(styleInfo.info[0].message) // "Consider using dot notation: name.first"

Advanced Usage

// Validate multiple expressions
const expressions = ['user.name', 'user.age > 18', 'users[.active == true].length']

expressions.forEach((expr, index) => {
  const result = validator.validate(expr, context)
  if (!result.isValid) {
    console.log(`Expression ${index + 1} has errors:`, result.errors)
  }
  if (result.warnings.length > 0) {
    console.log(`Expression ${index + 1} has warnings:`, result.warnings)
  }
})

// Custom validation with options
const result = validator.validate('   user.email   ', undefined, {
  allowUndefinedContext: true,
})

console.log(`Original: "${result.trimmedExpression}"`)
console.log(`Trimmed: "${result.trimmedExpression}"`)
console.log(`Valid: ${result.isValid}`)

Validator API Reference

validate(expression, context?, options?)

Parameters:

  • expression {string}: The Jexl expression to validate
  • context {object}: Optional context object for validation
  • options {object}: Optional validation options
    • allowUndefinedContext {boolean}: Allow undefined context properties (default: false)

Returns: ValidationResult object with validation details

ValidationResult Properties
  • isValid {boolean}: True if expression has no errors
  • errors {ValidationIssue[]}: Array of critical validation errors
  • warnings {ValidationIssue[]}: Array of non-critical warnings
  • info {ValidationIssue[]}: Array of informational suggestions
  • trimmedExpression {string}: Expression after whitespace trimming

Other implementations

  • PyJEXL - A Python-based JEXL parser and evaluator.
  • JexlApex - A Salesforce Apex JEXL parser and evaluator.

License

Jexl is licensed under the MIT license. Please see LICENSE.txt for full details.

Credits

Current Maintainer: Pawel Uchida-Psztyc (@jarrodek) - Modernized the library with TypeScript, enhanced type safety, and modern tooling.

Original Creator: Tom Shawver (@TomFrost) - Created the original Jexl library in 2015.

Contributors: Thanks to all the contributors who helped make the original Jexl library great.

The original Jexl was created at TechnologyAdvice in Nashville, TN.