@letsflow/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.
This fork extends the original specs, adding the following functionality;
Additionally, it adds the following functions:
Array
- find_first - Find the first element in an array that matches a condition
- find_last - Find the last element in an array that matches a condition
- items - Convert an object into an array of key-value pairs
- from_items - Convert an array of key-value pairs into an object
- group_by - Group elements of an array based on an expression
- zip - Combine two arrays into an array of pairs
- range - Generate a range of numbers or prefixed strings
String
- lower - Convert a string to lowercase
- upper - Convert a string to uppercase
- pad_left - Pad a string on the left
- pad_right - Pad a string on the right
- replace - Replace occurrences of a substring in a string
- split - Split a string into an array by a delimiter
- trim - Remove leading and trailing whitespace from a string
- trim_left - Remove leading whitespace from a string
- trim_right - Remove trailing whitespace from a string
JSON
- json_serialize - Serialize a JSON value to a string
- json_parse - Parse a JSON string into a JSON object
Cryptography
- sha256 - Calculate the SHA-256 hash of a string
- sha512 - Calculate the SHA-512 hash of a string
- uuid - Generate a UUID v5
Regex
- 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
- regex_count - Count the number of matches of a regular expression in a string
Misc
npm install @letsflow/jmespath
import { search } from '@letsflow/jmespath';
search(
{ foo: { bar: { baz: [0, 1, 2, 3, 4] } } },
"foo.bar.baz[2]"
);
// OUTPUTS: 2
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
.
The JMESPath language can do a lot more than select an element from a list. Here are a few more examples:
import { search } from '@letsflow/jmespath';
const document = {
foo: {
bar: {
baz: [0, 1, 2, 3, 4]
}
}
};
search(document, "foo.bar");
// OUTPUTS: { baz: [ 0, 1, 2, 3, 4 ] }
import { search } from '@letsflow/jmespath';
const document = {
"foo": [
{ "first": "a", "last": "b" },
{ "first": "c", "last": "d" }
]
};
search(document, "foo[*].first")
// OUTPUTS: [ 'a', 'c' ]
import { search } from '@letsflow/jmespath';
const document = {
"foo": [
{ "age": 20 },
{ "age": 25 },
{ "age": 30 },
{ "age": 35 },
{ "age": 40 }
]
}
search(document, "foo[?age > `30`]");
// OUTPUTS: [ { age: 35 }, { age: 40 } ]
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');
TreeInterpreter.search(ast, { foo: { bar: 'BAZ' } })
// RETURNS: "BAZ"
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
Use $
to access the document root.
search({foo: { bar: 999 }, baz: [1, 2, 3]}, '$.baz[*].[@, $.foo.bar]')
// OUTPUTS:
// [ [ 1, 999 ], [ 2, 999 ], [ 3, 999 ] ]
The let
expression allows you to bind variables that are evaluated in the context of a given lexical scope. This enables queries that can refer to elements defined outside of their current element.
search(
{
minimum: 2,
items: [
{ name: "apple", price: 2 },
{ name: "banana", price: 1 },
{ name: "cherry", price: 3 }
]
},
'let $t = minimum in items[?price >= $t].name'
)
// OUTPUTS:
// [ "apple", "cherry" ]
For more details, refer to JEP-18.
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]')
// OUTPUTS;
// [{"bar": 10}]
You can also use numbers in arithmetic operations
search({}, '16 + 26'); // 42
Syntax:
find_first(array, condition)
Description:
Finds the first element in array
that satisfies condition
. Returns null
if no match is found.
Example:
find_first([1, 2, 3, 4], @ > 2)
// 3
Syntax:
find_last(array, condition)
Description:
Finds the last element in array
that satisfies condition
. Returns null
if no match is found.
Example:
find_last([1, 2, 3, 4], @ > 2)
// 4
Syntax:
from_items(array)
Description:
Converts an array of [key, value]
pairs into an object.
Example:
from_items([["key1", "value1"], ["key2", "value2"]])
// { "key1": "value1", "key2": "value2" }
Syntax:
get(object, key, defaultValue?)
Description: Returns the value of a key in an object.
Example:
get({ key: 'value' }, 'key') // "value"
get({ key: 'value' }, 'missing') // null
get({ key: 'value' }, 'missing', 'default') // "default"
Syntax:
group_by(array, expression)
Description:
Groups elements of array
based on expression
and returns an object where keys are unique values of expression
and values are arrays of corresponding elements.
Example:
group_by([{id: 1, type: "A"}, {id: 2, type: "B"}, {id: 3, type: "A"}], type)
// { "A": [{id: 1, type: "A"}, {id: 3, type: "A"}], "B": [{id: 2, type: "B"}] }
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")
Syntax:
items(object)
Description:
Converts an object into an array of [key, value]
pairs.
Example:
items({ "key1": "value1", "key2": "value2" })
// [["key1", "value1"], ["key2", "value2"]]
Syntax:
json_parse(string)
Description:
Parses a JSON string into a JSON object.
Example:
json_parse("{\"key\":\"value\"}")
// { "key": "value" }
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\"}"
Syntax:
lower(string)
Description:
Converts a string to lowercase.
Example:
lower("Hello World")
// "hello world"
Syntax:
pad_left(string, length, char)
Description:
Pads string
on the left with char
until it reaches length
.
Example:
pad_left("42", 5, "0")
// "00042"
Syntax:
pad_right(string, length, char)
Description:
Pads string
on the right with char
until it reaches length
.
Example:
pad_right("42", 5, "0")
// "42000"
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"]
Syntax:
regex_count(regex, string)
Description: Counts the number of matches of a regular expression in a string.
Example:
regex_count('/\\w+/g', 'hello world')
// 2
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"]
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"]]
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"
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
Syntax:
replace(string, search, replacement)
Description:
Replaces occurrences of search
with replacement
in string
.
Example:
replace("hello world", "world", "universe")
// "hello universe"
Syntax:
sha256(string)
Description:
Calculates the SHA-256 hash of a string and returns it as a hexadecimal string.
Example:
sha256('hello')
// "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
Syntax:
sha512(string)
Description:
Calculates the SHA-512 hash of a string and returns it as a hexadecimal string.
Example:
sha512('hello')
// "9b71d224bd62f3785d96d46ad3ea3d73319b0c44e59b202205c5d235a0a6caa5a3b36f8c0ab9d45df9215bf07d4d1552c0b1f8bd2671c8a7a3d126f457d79d72"
Syntax:
split(string, delimiter)
Description:
Splits a string into an array using delimiter
.
Example:
split("a,b,c", ",")
// ["a", "b", "c"]
Syntax:
trim(string)
Description:
Removes leading and trailing whitespace from string
.
Example:
trim(" hello ")
// "hello"
Syntax:
trim_left(string)
Description:
Removes leading whitespace from string
.
Example:
trim_left(" hello ")
// "hello "
Syntax:
trim_right(string)
Description:
Removes trailing whitespace from string
.
Example:
trim_right(" hello ")
// " hello"
Syntax:
upper(string)
Description:
Converts a string to uppercase.
Example:
upper("Hello World")
// "HELLO WORLD"
Syntax:
uuid(name?, namespace?)
Description:
Generates a version 5 UUID.
UUID v5 is consistent. It creates a UUID based on the SHA hash of the input. This means that any given combination of input and namespace will result in the same UUID, every time.
Example:
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.
namespace
must be a UUID string. By default, it uses the NIL UUID.
The UUID RFC pre-defines four namespaces
- NameSpace_DNS:
6ba7b810-9dad-11d1-80b4-00c04fd430c8
- NameSpace_URL:
6ba7b811-9dad-11d1-80b4-00c04fd430c8
- NameSpace_OID:
6ba7b812-9dad-11d1-80b4-00c04fd430c8
- NameSpace_X500:
6ba7b814-9dad-11d1-80b4-00c04fd430c8
Syntax:
zip(array1, array2)
Description:
Combines two arrays into an array of pairs. The resulting array length is the shorter of the two input arrays.
Example:
zip([1, 2, 3], ["a", "b", "c"])
// [[1, "a"], [2, "b"], [3, "c"]]
The example above only shows 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.
The full JMESPath specification can be found on the JMESPath site.