SkillAgentSearch skills...

Jexl

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

Install / Use

/learn @TomFrost/Jexl
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Jexl Build Status

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

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

// Or synchronously!
console.log(jexl.evalSync('assoc[.first == "Lana"].last')) // 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"

// 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

// 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')
danger.evalSync({ place: 'zone' }) // Danger zone
danger.evalSync({ place: 'ZONE!!!' }) // Danger ZONE!!! (Doesn't recompile the expression!)

Play with it

Installation

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

Install from npm:

npm install jexl --save

or yarn:

yarn add jexl

and use it:

const jexl = require('jexl')

Async vs Sync: Which to use

There is little performance difference between eval and evalSync. The functional difference is that, if eval is used, Jexl can be customized with asynchronous operators, transforms, and even wait for unresolved promises in the context object with zero additional overhead or handling on the programmer's part. evalSync eliminates those advantages, exposing the expression to raw Promise objects if any are returned as the result of a custom transform or operator. However, if your application doesn't require async methods, the evalSync API can be simpler to use.

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

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

Related Skills

View on GitHub
GitHub Stars648
CategoryDevelopment
Updated10h ago
Forks103

Languages

JavaScript

Security Score

95/100

Audited on Mar 31, 2026

No findings