JMESPath

@letsflow/jmespath is a TypeScript implementation of the JMESPath spec with additional functionality for LetsFlow workflows.

JMESPath is a query language for JSON. It takes a JSON document as input and transforms it into another JSON document given a JMESPath expression.


This fork extends the original specs, adding the following functionality;

Additionally, it adds the following functions:

  • if - Conditional expression

  • range - Generate a range of numbers or prefixed strings

  • to_object - Convert an array of key-value pairs into an object

  • json_serialize - Serialize a JSON value to a string

  • json_parse - Parse a JSON string into a JSON object

  • sha256 - Calculate the SHA-256 hash of a string

  • sha512 - Calculate the SHA-512 hash of a string

  • uuid - Generate a UUID

  • regex_test - Test if a string matches a regular expression

  • regex_match - Return the first match of a regular expression in a string

  • regex_match_all - Return all matches of a regular expression in a string

  • regex_replace - Replace parts of a string matching a regular expression with a replacement string

Installation

yarn add @letsflow/jmespath

Usage

search(data: JSONValue, expression: string): JSONValue

import { search } from '@letsflow/jmespath';

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

// OUTPUTS: 2

The example above only shows a small example 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.

The full JMESPath specification can be found on the JMESPath site.

Extension to the original specs

The LetsFlow fork of JMESPath adds the following functionality, which is not in the original specs.

Custom functions

registerFunction(functionName: string, customFunction: RuntimeFunction, signature: InputSignature[]): void

Extend the list of built-in JMESpath expressions with your own functions.

import {search, registerFunction, TYPE_NUMBER} from '@letsflow/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

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

Root value access

Use $ to access the document root.

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

Number literals

Numbers in the root scope are treated as number literals. This means that you don't need to quote numbers with backticks.

search([{"bar": 1}, {"bar": 10}], '[?bar==10]')
// [{"bar": 10}]

You can also use numbers in arithmetic operations

search({}, '16 + 26'); // 42

Additional Functions

if

Syntax:

if(condition, thenValue, elseValue?)

Description: Returns thenValue if condition is true, otherwise returns elseValue. If elseValue is not provided, it defaults to null.

Example:

if(@ > 10, "large", "small")

range

Syntax:

range(start, end, prefix?)

Description: Generates an array of numbers or prefixed strings from start to end - 1. If prefix is provided, each number is prefixed.

Example:

range(5)                // [0, 1, 2, 3, 4]
range(1, 5)             // [1, 2, 3, 4]
range(1, 5, 'item_')    // ["item_1", "item_2", "item_3", "item_4"]

to_object

Syntax:

to_object(entries)

Description: Converts an array of key-value pairs into an object.

Example:

to_object([['key1', 'value1'], ['key2', 'value2']])
// { "key1": "value1", "key2": "value2" }

[ 'value1', 'value2'] | to_object(zip(range(1, length(@) + 1, 'key'), @))
// { "key1": "value1", "key2": "value2" }

json_serialize

Syntax:

json_serialize(value)

Uses a deterministic version of JSON.stringify to serialize the value.

Description: Serializes a JSON value to a string.

Example:

json_serialize({ key: 'value' })
// "{\"key\":\"value\"}"

json_parse

Syntax:

json_parse(string)

Description: Parses a JSON string into a JSON object.

Example:

json_parse("{\"key\":\"value\"}")
// { "key": "value" }

sha256

Syntax:

sha256(string)

Description: Calculates the SHA-256 hash of a string and returns it as a hexadecimal string.

Example:

sha256('hello')
// "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

sha512

Syntax:

sha512(string)

Description: Calculates the SHA-512 hash of a string and returns it as a hexadecimal string.

Example:

sha512('hello')
// "9b71d224bd62f3785d96d46ad3ea3d73319b0c44e59b202205c5d235a0a6caa5a3b36f8c0ab9d45df9215bf07d4d1552c0b1f8bd2671c8a7a3d126f457d79d72"

uuid

Syntax:

uuid(name?, namespace?)

Description: Generates a UUID. If name and (optionally) namespace are provided, generates a version 5 UUID; otherwise, generates a version 4 UUID.

Example:

uuid() // Random v4 UUID
uuid('example') // v5 UUID
uuid('example', '6ba7b810-9dad-11d1-80b4-00c04fd430c8') // v5 UUID with namespace

name must be a string. Use json_serialize() to convert a JSON object to a string.

regex_test

Syntax:

regex_test(regex, string)

Description: Tests if a string matches a given regular expression.

Example:

regex_test('/^hello/', 'hello world') // true

regex_test('/^hello/', 'HELLO world') // false
regex_test('/^hello/i', 'HELLO world') // true

regex_match

Syntax:

regex_match(regex, string)

Description: Returns the first match of a regular expression in a string as an array.

Example:

regex_match('/hello (\\w+)/', 'hello world')
// ["hello world", "world"]

regex_match('/\\w+/g', 'hello world')
// ["hello", "world"]

regex_match_all

Syntax:

regex_match_all(regex, string)

Description: Returns all matches of a regular expression in a string as an array of arrays.

Example:

regex_match_all('/(\\w+)=(\d+)/g', 'foo=24 bar=99')
// [["foo=24", "foo", "24"], ["bar=99", "bar", "99"]]

regex_replace

Syntax:

regex_replace(regex, replacement, string)

Description: Replaces parts of a string matching a regular expression with a replacement string.

Example:

regex_replace('/world/', 'universe', 'hello world')
// "hello universe"

Last updated