Skip to content

About

TypeScript implementation of JMESPath Community.

Resources

Stars

13 stars

Watchers

3 watching

Forks

Repository files navigation

Build

@jmespath-community/jmespath

@jmespath-community/jmespath is a TypeScript implementation of the JMESPath spec.

JMESPath is a query language for JSON. It will take a JSON document as input and transform it into another JSON document given a JMESPath expression.

Table of Contents

Installation

npm install @jmespath-community/jmespath

Usage

ESM (Node.js)

import { search } from "@jmespath-community/jmespath";

const data = { foo: { bar: { baz: [0, 1, 2, 3, 4] } } };
const result = search(data, "foo.bar.baz[2]");

console.log(result);
// Outputs: 2

CommonJS (Node.js)

const { search } = require("@jmespath-community/jmespath");

const data = { foo: { bar: { baz: [0, 1, 2, 3, 4] } } };
const result = search(data, "foo.bar.baz[2]");

console.log(result);
// Outputs: 2

Browser (UMD)

You can include the UMD build directly in your HTML file. The library will be available under the jmespath global variable.

<script src="https://unpkg.com/@jmespath-community/jmespath/dist/index.umd.min.js"></script>
<script>
  const data = { foo: { bar: { baz: [0, 1, 2, 3, 4] } } };
  const result = jmespath.search(data, "foo.bar.baz[2]");
  console.log(result);
  // Outputs: 2
</script>

API

search<T = JSONValue, D = unknown>(data: D, expression: string, options?: Options): T

In the example we gave the search function input data of {foo: {bar: {baz: [0, 1, 2, 3, 4]}}} as well as the JMESPath expression foo.bar.baz[2], and the search function evaluated the expression against the input data to produce the result 2.

Accepts custom TypeScript interfaces directly without casting:

interface User {
  name: string;
  age: number;
}

const user: User = { name: "Alice", age: 30 };
const name = search<string>(user, "name");
// name is typed as string

The JMESPath language can do a lot more than select an element from a list. Here are a few more examples:

import { search } from "@jmespath-community/jmespath";

/* --- EXAMPLE 1 --- */

let JSON_DOCUMENT = {
  foo: {
    bar: {
      baz: [0, 1, 2, 3, 4],
    },
  },
};

search(JSON_DOCUMENT, "foo.bar");
// OUTPUTS: { baz: [ 0, 1, 2, 3, 4 ] }

/* --- EXAMPLE 2 --- */

JSON_DOCUMENT = {
  foo: [
    { first: "a", last: "b" },
    { first: "c", last: "d" },
  ],
};

search(JSON_DOCUMENT, "foo[*].first");
// OUTPUTS: [ 'a', 'c' ]

/* --- EXAMPLE 3 --- */

JSON_DOCUMENT = {
  foo: [{ age: 20 }, { age: 25 }, { age: 30 }, { age: 35 }, { age: 40 }],
};

search(JSON_DOCUMENT, "foo[?age > `30`]");
// OUTPUTS: [ { age: 35 }, { age: 40 } ]

Automatic Type Inference

Literal path queries automatically infer property types without requiring manual type parameters or type assertions:

const user = {
  profile: { name: "Alice", email: "alice@example.com" },
  tags: ["admin", "dev"],
};

const name = search(user, "profile.name"); // inferred as string
const tag = search(user, "tags[0]");       // inferred as string
const tags = search(user, "tags[*]");      // inferred as string[]

Runtime Security Options

Pass options to restrict execution limits on untrusted queries:

search(data, expression, {
  maxDepth: 32,                            // expression and evaluation recursion limit
  maxNodes: 128,                           // maximum AST node count
  maxOutputSize: 1000,                     // maximum elements or characters in results
  allowedFunctions: ['length', 'contains'], // allow-list specific built-ins
  disallowedFunctions: ['custom_heavy'],   // deny-list specific built-ins
  timeoutMs: 100,                          // query execution time limit in ms
});

searchStream<T = JSONValue, D = unknown>(source: StreamSource<D>, expression: string | ExpressionNode, options?: StreamOptions): AsyncGenerator<T, void, unknown>

Evaluates a JMESPath expression incrementally over an AsyncIterable, ReadableStream, or Node.js stream.Readable, yielding matching results without buffering large datasets in memory:

import jmespath from "@jmespath-community/jmespath";

async function* getEvents() {
  yield { id: 1, level: "info", message: "connected" };
  yield { id: 2, level: "error", message: "disk full" };
}

for await (const msg of jmespath.searchStream(getEvents(), "[?level == 'error'].message")) {
  console.log(msg); // "disk full"
}

Supports automatic NDJSON / JSON lines chunk decoding (parseJson: 'auto'), chunk flattening, precompiled ASTs, and early termination:

for await (const item of jmespath.searchStream(logStream, "[:10]", { limit: 10 })) {
  console.log(item);
}

compile(expression: string): ExpressionNodeTree

You can precompile all your expressions ready for use later on. The compile function takes a JMESPath expression and returns an abstract syntax tree that can be used by the TreeInterpreter function

import { compile, TreeInterpreter } from "@jmespath-community/jmespath";

const ast = compile("foo.bar");

const result = TreeInterpreter.search(ast, { foo: { bar: "BAZ" } });
// RETURNS: "BAZ"

compileQuery<T = JSONValue, D = unknown>(expression: string, options?: Options): CompiledQuery<T, D>

Precompiles an expression into a reusable, callable function:

import { compileQuery } from "@jmespath-community/jmespath";

const getName = compileQuery<string, { user: { name: string } }>("user.name");

const result = getName({ user: { name: "Alice" } });
// Outputs: "Alice"

validate(expression: string, options?: Options): ValidationResult

Validates an expression without throwing exceptions, checking syntax and security options:

import { validate } from "@jmespath-community/jmespath";

const result = validate("foo.bar");
// { valid: true, ast: { type: 'Subexpression', ... } }

const invalid = validate("foo.", { maxNodes: 10 });
// { valid: false, error: 'Syntax error: ...' }

EXTENSIONS TO ORIGINAL SPEC

  1. Register your own custom functions

    Enhanced Function Registry API

    The library provides both backward-compatible and enhanced APIs for registering custom functions with improved developer experience, type safety, and flexible override behavior.

    Basic Usage (Backward Compatible)
    import { search, registerFunction, TYPE_NUMBER } from "@jmespath-community/jmespath";
    
    search({ foo: 60, bar: 10 }, "divide(foo, bar)");
    // THROWS ERROR: Error: Unknown function: divide()
    
    registerFunction(
      "divide", // FUNCTION NAME
      (resolvedArgs) => {
        // CUSTOM FUNCTION
        const [dividend, divisor] = resolvedArgs;
        return dividend / divisor;
      },
      [{ types: [TYPE_NUMBER] }, { types: [TYPE_NUMBER] }], //SIGNATURE
    );
    
    search({ foo: 60, bar: 10 }, "divide(foo, bar)");
    // OUTPUTS: 6
    Enhanced Registry API with Type Safety
    import { register, search, TYPE_NUMBER } from "@jmespath-community/jmespath";
    
    // TypeScript prevents registering built-in functions at compile time
    // register('sum', myFunc, signature); // TypeScript error!
    
    // Enhanced registration with better error handling
    const result = register('multiply', ([a, b]) => a * b, [
      { types: [TYPE_NUMBER] },
      { types: [TYPE_NUMBER] }
    ]);
    
    if (result.success) {
      console.log(result.message); // "Function multiply() registered successfully"
    } else {
      console.error(result.message); // Detailed error information
    }
    Override Existing Functions
    import { registerFunction, register } from "@jmespath-community/jmespath";
    
    // Option 1: Using registerFunction with options
    registerFunction('myFunc', () => 'first', []);
    registerFunction('myFunc', () => 'second', [], { override: true, warn: true });
    // Console: "Warning: Overriding existing function: myFunc()"
    
    // Option 2: Using enhanced register API
    const result = register('myFunc', () => 'third', [], { override: true });
    console.log(result.message); // "Function myFunc() overridden successfully"
    Registry Management
    import {
      isRegistered,
      getRegisteredFunctions,
      getCustomFunctions,
      unregisterFunction,
      clearCustomFunctions
    } from "@jmespath-community/jmespath";
    
    // Check if function exists
    console.log(isRegistered('sum')); // true (built-in)
    console.log(isRegistered('myFunc')); // true (if registered)
    
    // Get all registered functions
    const allFunctions = getRegisteredFunctions();
    console.log(allFunctions); // ['abs', 'avg', 'ceil', ..., 'myFunc']
    
    // Get only custom functions
    const customFunctions = getCustomFunctions();
    console.log(customFunctions); // ['myFunc', 'divide', ...]
    
    // Unregister custom function (built-ins cannot be unregistered)
    const removed = unregisterFunction('myFunc');
    console.log(removed); // true if successful
    
    // Clear all custom functions
    clearCustomFunctions();
    console.log(getCustomFunctions()); // []
    Optional Arguments

    Optional arguments are supported by setting {..., optional: true} in argument signatures

    registerFunction(
      "divide",
      (resolvedArgs) => {
        const [dividend, divisor] = resolvedArgs;
        return dividend / (divisor ?? 1); //OPTIONAL DIVISOR THAT DEFAULTS TO 1
      },
      [{ types: [TYPE_NUMBER] }, { types: [TYPE_NUMBER], optional: true }], //SIGNATURE
    );
    
    search({ foo: 60, bar: 10 }, "divide(foo)");
    // OUTPUTS: 60
  2. Root value access with $ symbol

search({ foo: { bar: 999 }, baz: [1, 2, 3] }, "$.baz[*].[@, $.foo.bar]");

// OUTPUTS:
// [ [ 1, 999 ], [ 2, 999 ], [ 3, 999 ] ]

More Resources

The example above only show a small amount of what a JMESPath expression can do. If you want to take a tour of the language, the best place to go is the JMESPath Tutorial.

One of the best things about JMESPath is that it is implemented in many different programming languages including python, ruby, php, lua, etc. To see a complete list of libraries, check out the JMESPath libraries page.

And finally, the full JMESPath specification can be found on the JMESPath site.

Experimental Features

Ternary Operations (? :)

Supported Version: 1.1.6

Experimental support for ternary operations has been added, allowing for conditional logic within your JMESPath expressions. The syntax is condition ? value_if_true : value_if_false.

  • Condition: The expression before the ?. JMESPath determines truthiness based on the evaluated value:
    • true is truthy.
    • Any non-empty object, array, or string is truthy.
    • Any non-zero number is truthy.
    • false, null, empty objects {}, empty arrays [], and empty strings '' are falsy.
  • Value if true: The expression between the ? and :. This is evaluated and returned if the condition is truthy.
  • Value if false: The expression after the :. This is evaluated and returned if the condition is falsy.

Examples:

Basic usage:

search({ is_active: true, user: "Alice" }, "is_active ? user : 'Guest'");
// OUTPUTS: "Alice"

search({ is_active: false, user: "Bob" }, "is_active ? user : 'Guest'");
// OUTPUTS: "Guest"

Truthiness with different types:

search({ data: [1, 2] }, "data ? 'has_data' : 'no_data'");
// OUTPUTS: "has_data"

search({ data: [] }, "data ? 'has_data' : 'no_data'");
// OUTPUTS: "no_data"

search({ count: 5 }, "count ? 'count_present' : 'no_count'");
// OUTPUTS: "count_present"

search({ count: 0 }, "count ? 'count_present' : 'no_count'");
// OUTPUTS: "no_count"

Nested Ternaries:

search({ a: true, b: false, val1: 10, val2: 20, val3: 30 }, "a ? (b ? val1 : val2) : val3");
// OUTPUTS: 20

This feature is currently experimental and its syntax or behavior might change in future releases.

About

TypeScript implementation of JMESPath Community.

Resources

Stars

13 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages