UNPKG

json-p3

Version:

JSONPath, JSON Pointer and JSON Patch

1,717 lines (1,634 loc) 156 kB
/* * json-p3 version 2.2.2 * https://github.com/jg-rp/json-p3 * * MIT License * * Copyright (c) 2025 James Prior * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ 'use strict'; /** * Base class for all JSONPath errors. */ class JSONPathError extends Error { constructor(message, token) { super(message); this.message = message; this.token = token; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPathError"; this.message = withErrorContext(message, token); } } function withErrorContext(message, token) { if (token.input.length <= 9) { return `${message} ('${token.input}':${token.index})`; } if (token.index > token.input.length - 5) { return `${message} ('${token.input.slice(token.input.length - 9)}':${token.index})`; } if (token.index - 4 < 0) { return `${message} ('${token.input.slice(0, 9)}':${token.index})`; } return `${message} ('${token.input.slice(token.index - 4, token.index + 5)}':${token.index})`; } /** * Error thrown due to unexpected, internal path tokenization problems. */ class JSONPathLexerError extends JSONPathError { constructor(message, token) { super(message, token); this.message = message; this.token = token; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPathLexerError"; this.message = withErrorContext(message, token); } } /** * Error thrown due to type errors when evaluating filter expressions. */ class JSONPathTypeError extends JSONPathError { constructor(message, token) { super(message, token); this.message = message; this.token = token; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPathTypeError"; this.message = withErrorContext(message, token); } } /** * Error thrown due to out of range indices. */ class JSONPathIndexError extends JSONPathError { constructor(message, token) { super(message, token); this.message = message; this.token = token; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPathIndexError"; this.message = withErrorContext(message, token); } } /** * Error thrown when attempting to retrieve a filter function that has not * been registered. */ class UndefinedFilterFunctionError extends JSONPathError { constructor(message, token) { super(message, token); this.message = message; this.token = token; Object.setPrototypeOf(this, new.target.prototype); this.name = "UndefinedFilterFunctionError"; this.message = withErrorContext(message, token); } } /** * Error thrown due to syntax errors found during parsing a JSONPath query. */ class JSONPathSyntaxError extends JSONPathError { constructor(message, token) { super(message, token); this.message = message; this.token = token; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPathSyntaxError"; this.message = withErrorContext(message, token); } } /** * Error thrown when the maximum recursion depth is reached. */ class JSONPathRecursionLimitError extends JSONPathError { constructor(message, token) { super(message, token); this.message = message; this.token = token; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPathRecursionLimitError"; this.message = withErrorContext(message, token); } } /** * Error thrown due to invalid I-Regexp syntax. */ class IRegexpError extends Error { constructor(message) { super(message); this.message = message; Object.setPrototypeOf(this, new.target.prototype); this.name = "IRegexpError"; } } /** * Common types and type predicates. */ /** * A JSON-like value. */ /** * A type predicate for the Array object. */ function isArray(value) { return Array.isArray(value); } /** * A type predicate for object. */ function isObject(value) { const _type = typeof value; return value !== null && _type === "object" || _type === "function" ? true : false; } /** * A type predicate for a string primitive. */ function isString(value) { return typeof value === "string"; } /** * A type predicate for a number primitive. */ function isNumber(value) { return typeof value === "number"; } /** * Deep equality of JSON-like values. * * No attempt is made to handle function objects, recursive data * structures, NaNs, sparse arrays, primitive wrapper objects.... * * We're not using JSON.stringify because we want objects with the same * entries in a different order to compare equal. */ // eslint-disable-next-line sonarjs/cognitive-complexity function deepEquals(a, b) { if (a === b) { return true; } if (Array.isArray(a)) { if (Array.isArray(b)) { if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { if (!deepEquals(a[i], b[i])) { return false; } } return true; } return false; } else if (isObject(a) && isObject(b)) { const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) { return false; } for (const key of keysA) { if (!deepEquals(a[key], b[key])) { return false; } } return true; } return false; } /** * The type of a JSONPath filter function parameter or return value, as * described in See section 2.4.1 of RFC 9535. */ let FunctionExpressionType = /*#__PURE__*/function (FunctionExpressionType) { FunctionExpressionType["ValueType"] = "ValueType"; FunctionExpressionType["LogicalType"] = "LogicalType"; FunctionExpressionType["NodesType"] = "NodesType"; return FunctionExpressionType; }({}); /** * A JSONPath filter function definition. */ /** * Base class for all JSON Pointer errors. */ class JSONPointerError extends Error { constructor(message) { super(message); this.message = message; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPointerError"; } } /** * Base class for JSON Pointer resolution errors. */ class JSONPointerResolutionError extends JSONPointerError { constructor(message) { super(message); this.message = message; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPointerResolutionError"; } } /** * Error thrown due to an out of range index when resolving a JSON Pointer. */ class JSONPointerIndexError extends JSONPointerResolutionError { constructor(message) { super(message); this.message = message; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPointerIndexError"; } } /** * Error thrown due to a missing property when resolving a JSON Pointer. */ class JSONPointerKeyError extends JSONPointerResolutionError { constructor(message) { super(message); this.message = message; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPointerKeyError"; } } /** * Error thrown due to invalid JSON Pointer syntax. */ class JSONPointerSyntaxError extends JSONPointerError { constructor(message) { super(message); this.message = message; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPointerSyntaxError"; } } /** * Error thrown when trying to resolve a property or index against a primitive value. */ class JSONPointerTypeError extends JSONPointerResolutionError { constructor(message) { super(message); this.message = message; Object.setPrototypeOf(this, new.target.prototype); this.name = "JSONPointerTypeError"; } } /** * The symbol indicating the absence of a JSON value. */ const UNDEFINED = Symbol.for("jsonpointer.undefined"); /** * Identify a single value in JSON-like data, as per RFC 6901. */ class JSONPointer { #pointer; /** * @param pointer - A string representation of a JSON Pointer. */ constructor(pointer) { this.tokens = this.parse(pointer); this.#pointer = JSONPointer.encode(this.tokens); } static encode(tokens) { if (!tokens.length) return ""; return ( // eslint-disable-next-line prefer-template "/" + tokens.map(token => token.replaceAll("~", "~0").replaceAll("/", "~1")).join("/") ); } /** * Resolve this pointer against JSON-like data _value_. * * @param value - The target JSON-like value, possibly loaded using * `JSON.parse()`. * @param fallback - A default value to return if _value_ has no * path matching `pointer`. * @returns The value identified by _pointer_ or, if given, the fallback * value in the even of a `JSONPointerResolutionError`. * * @throws {@link JSONPointerResolutionError} * If the value pointed to by _pointer_ does not exist in _value_, and * no fallback value is given. */ resolve(value) { let fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : UNDEFINED; try { return this.tokens.reduce(this.getItem.bind(this), value); } catch (error) { if (error instanceof JSONPointerResolutionError && fallback !== UNDEFINED) { return fallback; } throw error; } } /** * * @param value - * @returns */ resolveWithParent(value) { if (!this.tokens.length) return [UNDEFINED, this.resolve(value)]; const parent = this.tokens.slice(0, this.tokens.length - 1).reduce(this.getItem.bind(this), value); try { return [parent, this.getItem(parent, this.tokens[this.tokens.length - 1], this.tokens.length - 1)]; } catch (error) { if (error instanceof JSONPointerIndexError || error instanceof JSONPointerKeyError) { return [parent, UNDEFINED]; } throw error; } } /** * * @returns */ toString() { return this.#pointer; } /** * Return _true_ if this pointer points to a child of _pointer_. */ isRelativeTo(pointer) { return pointer.tokens.length < this.tokens.length && this.tokens.slice(0, pointer.tokens.length).every((t, i) => t === pointer.tokens[i]); } parse(pointer) { if (pointer.length && !pointer.startsWith("/")) { throw new JSONPointerSyntaxError(`"${pointer}" pointers must start with a slash or be the empty string`); } return pointer.split("/").map(token => token.replaceAll("~1", "/").replaceAll("~0", "~")).slice(1); } // eslint-disable-next-line sonarjs/cognitive-complexity getItem(val, token, idx) { // NOTE: // - string primitives "have own" indices and `length`. // - Arrays have a `length` property. // - A property might exist with the value `undefined` or `null`. // - obj[1] is equivalent to obj["1"]. if (isArray(val)) { if (token !== "length" && Object.hasOwn(val, token)) { return val[Number(token)]; } else if (token.startsWith("#")) { // handle non-standard '#' from relative json pointer const maybeIndex = token.slice(1); if (RE_INT.test(maybeIndex) && Object.hasOwn(val, maybeIndex)) { return Number(maybeIndex); } else { throw new JSONPointerIndexError(`index out of range '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`); } } else { throw new JSONPointerIndexError(`index out of range '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`); } } else if (isObject(val)) { if (Object.hasOwn(val, token)) { return val[token]; } else if (token.startsWith("#") && Object.hasOwn(val, token.slice(1))) { // handle non-standard '#' from relative json pointer return token.slice(1); } else { throw new JSONPointerKeyError(`no such property '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`); } } throw new JSONPointerTypeError(`found primitive value, expected an object '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`); } _join(pointer) { if (!isString(pointer)) { throw new JSONPointerTypeError(`join() requires string arguments, found ${typeof pointer}`); } if (pointer.startsWith("/")) { return new JSONPointer(pointer); } const tokens = this.tokens.concat(pointer.split("/").map(token => token.replaceAll("~1", "/").replaceAll("~0", "~"))); return new JSONPointer(JSONPointer.encode(tokens)); } /** * Join this pointer with _tokens_. * * @param tokens - JSON Pointer strings, possibly without leading slashes. * If a token or "part" does have a leading slash, the previous pointer is * ignored and a new `JSONPointer` is created, then processing of the * remaining tokens continues. * * @returns A new JSON Pointer that is the concatenation of all tokens or * "parts". */ join() { for (var _len = arguments.length, tokens = new Array(_len), _key = 0; _key < _len; _key++) { tokens[_key] = arguments[_key]; } if (!tokens.length) { return this; } // eslint-disable-next-line @typescript-eslint/no-this-alias let pointer = this; for (const tok of tokens) { pointer = pointer._join(tok); } return pointer; } /** * Return _true_ if this pointer can be resolved against _value_. * * Note that `JSONPointer.resolve()` can return legitimate falsy values * that form part of the target JSON document. This method will return * `true` if a falsy value is found. */ exists(value) { try { this.resolve(value); } catch (error) { if (error instanceof JSONPointerResolutionError) { return false; } throw error; } return true; } /** * Return this pointer's parent as a new `JSONPointer`. * * If this pointer points to the document root, _this_ is returned. */ parent() { if (!this.tokens.length) { return this; } return new JSONPointer(JSONPointer.encode(this.tokens.slice(0, this.tokens.length - 1))); } to(rel) { const relativePointer = isString(rel) ? new RelativeJSONPointer(rel) : rel; return relativePointer.to(this); } } const RE_RELATIVE_POINTER = /(?<ORIGIN>\d+)(?<INDEX_G>(?<SIGN>[+-])(?<INDEX>\d))?(?<POINTER>.*)/s; const RE_INT = /(0|[1-9][0-9]*)/; /** * A relative JSON Pointer. * * See https://datatracker.ietf.org/doc/html/draft-hha-relative-json-pointer */ class RelativeJSONPointer { /** * * @param rel - */ constructor(rel) { [this.origin, this.index, this.pointer] = this.parse(rel); } /** * * @returns */ toString() { const sign = this.index > 0 ? "+" : ""; const index = this.index === 0 ? "" : `${sign}${this.index}`; return `${this.origin}${index}${this.pointer}`; } /** * * @param pointer - */ to(pointer) { const p = isString(pointer) ? new JSONPointer(pointer) : pointer; // move to origin if (this.origin > p.tokens.length) { throw new JSONPointerIndexError(`origin (${this.origin}) exceeds root (${p.tokens.length})`); } const tokens = this.origin < 1 ? p.tokens.slice() : p.tokens.slice(0, -this.origin); // array index offset if (this.index && tokens.length && this.isIntLike(tokens.at(-1))) { const newIndex = Number(tokens.at(-1)) + this.index; if (newIndex < 0) { throw new JSONPointerIndexError(`index offset out of range (${newIndex})`); } tokens[tokens.length - 1] = String(newIndex); } // pointer or index/property if (this.pointer instanceof JSONPointer) { tokens.push(...this.pointer.tokens); } else { tokens[tokens.length - 1] = `#${tokens[tokens.length - 1]}`; } return new JSONPointer(JSONPointer.encode(tokens)); } parse(rel) { const match = RE_RELATIVE_POINTER.exec(rel); if (!match || !match.groups) { throw new JSONPointerSyntaxError("failed to parse relative pointer"); } // steps to move const origin = this.parseInt(match.groups.ORIGIN); // optional index manipulation let index = 0; if (match.groups["INDEX_G"]) { index = this.parseInt(match.groups.INDEX); if (index === 0) { throw new JSONPointerSyntaxError("index offset can't be zero"); } if (match.groups.SIGN === "-") { index = -index; } } // pointer or '#'. an empty string is OK. if (match.groups.POINTER === "#") { return [origin, index, "#"]; } return [origin, index, new JSONPointer(match.groups.POINTER)]; } parseInt(s) { if (s.startsWith("0") && s.length > 1) { throw new JSONPointerSyntaxError("unexpected leading zero"); } if (RE_INT.test(s)) { return Number(s); } throw new JSONPointerSyntaxError(`expected an integer, found '${s}'`); } isIntLike(value) { if (value === undefined || isNumber(value)) { return true; } else { return RE_INT.test(value); } } } /** * Resolve JSON Pointer _pointer_ against JSON-like data _value_. * * @param pointer - A string representation of a JSON pointer. * @param value - The target JSON-like value, possibly loaded using * `JSON.parse()`. * @param fallback - A default value to return if _value_ has no * path matching `pointer`. * @returns The value identified by _pointer_ or, if given, the fallback * value in the even of a `JSONPointerResolutionError`. * * @throws {@link JSONPointerResolutionError} * If the value pointed to by _pointer_ does not exist in _value_, and * no fallback value is given. * * @throws {@link JSONPointerSyntaxError} * If _pointer_ is malformed according to RFC 6901. */ function resolve(pointer, value) { let fallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : UNDEFINED; return new JSONPointer(pointer).resolve(value, fallback); } var index$3 = /*#__PURE__*/Object.freeze({ __proto__: null, JSONPointer: JSONPointer, JSONPointerError: JSONPointerError, JSONPointerIndexError: JSONPointerIndexError, JSONPointerKeyError: JSONPointerKeyError, JSONPointerResolutionError: JSONPointerResolutionError, JSONPointerSyntaxError: JSONPointerSyntaxError, JSONPointerTypeError: JSONPointerTypeError, RelativeJSONPointer: RelativeJSONPointer, UNDEFINED: UNDEFINED, resolve: resolve }); /** * An identifier that is allowed in both JS and JSONPath. * JSONPath identifiers are generally much more permissive than JS ones, but * they don't allow the character "$", so we take the intersection of the two * when deciding whether to use dot shorthand for canonical serialization of * simple names. */ const SHORTHAND_COMPATIBLE_IDENTIFIER = /^[\p{ID_Start}_]\p{ID_Continue}*$/u; /** Usable in a quoted path. */ function toQuoted(name) { return name.includes("'") && !name.includes('"') ? JSON.stringify(name) : toCanonical(name); } /** Usable in a normalized path. */ function toCanonical(name) { return `'${JSON.stringify(name).slice(1, -1).replaceAll('\\"', '"').replaceAll("'", "\\'")}'`; } /** Usable in a shorthand path. */ function toShorthand(name) { return SHORTHAND_COMPATIBLE_IDENTIFIER.test(name) ? name : null; } const Nothing = Symbol.for("jsonpath.nothing"); /** * ValueType for JSONPath function expression tye system. */ /** * Object passed to `FilterExpression.evaluate()`. */ /** * A type predicate for an object with a string property. */ function hasStringKey(value, key) { return isObject(value) && Object.hasOwn(value, key); } const KEY_MARK = "\x02"; /** * Options for serializing paths. */ const defaultSerializationOptions = { form: "pretty" }; /** * The pair of a JSON value and its location found in the target JSON value. */ class JSONPathNode { /** * @param value - The JSON value found at _location_. * @param location - The parts of a normalized path to _value_. * @param root - The target value at the top of the JSON node tree. */ constructor(value, location, root) { this.value = value; this.location = location; this.root = root; } /** * @deprecated Use {@link getPath} with `options.form` set to `canonical` instead. */ get path() { return this.getPath({ form: "canonical" }); } /** * Get the path to this node in the target JSON value. * * Given that the path refers to the singular current node, the returned path * will always be a normalized path if `options.form` is set to `canonical`, * following section 2.7 of RFC 9535. */ getPath(options) { const opts = { ...defaultSerializationOptions, ...options }; return ( // eslint-disable-next-line prefer-template "$" + this.location.map(s => isString(s) ? this.decodeNameLocation(s, opts) : `[${s}]`).join("") ); } /** * Return this node's location as a {@link JSONPointer}. */ toPointer() { if (!this.location.length) { return new JSONPointer(""); } return new JSONPointer(JSONPointer.encode(this.location.map(String))); } decodeNameLocation(name, options) { const normalized = options.form === "canonical"; const serialize = normalized ? toCanonical : toQuoted; const hasKeyMark = name.startsWith(KEY_MARK); if (hasKeyMark) name = name.slice(1); const shorthand = toShorthand(name); if (hasKeyMark) { return normalized || shorthand == null ? `[~${serialize(name)}]` : `.~${shorthand}`; } return normalized || shorthand == null ? `[${serialize(name)}]` : `.${shorthand}`; } } /** * */ class JSONPathNodeList { constructor(nodes) { this.nodes = nodes; } /** * @returns an iterator over nodes in the list. */ [Symbol.iterator]() { return this.nodes[Symbol.iterator](); } /** * @returns `true` if the node list is empty. */ empty() { return this.nodes.length === 0; } /** * @returns An array containing the values at each node in the list. * * @see {@link valuesOrSingular} to unpack the array if there is only * one node in the list. */ values() { return this.nodes.map(node => node.value); } /** * Like {@link values}, but returns the node's value is there is only one * node in the list. */ valuesOrSingular() { if (this.nodes.length === 1) return this.nodes[0].value; return this.nodes.map(node => node.value); } /** * @returns An array of locations for each node in the node list. * * A location is an array of property names and array indices that were * required to reach the node's value in the target JSON value. */ locations() { return this.nodes.map(node => node.location); } /** * @returns An array of normalized path strings for each node in the list. * * A normalized path contains only property name and index selectors, and * always uses bracketed segments, never shorthand selectors. */ paths(options) { return this.nodes.map(node => node.getPath(options)); } /** * @returns An array of {@link JSONPointer} instances, one for each node * in the list. */ pointers() { return this.nodes.map(node => node.toPointer()); } /** * @returns The number of nodes in the node list. */ get length() { return this.nodes.length; } } /** * Base class for all filter expressions. */ class FilterExpression { constructor(token) { this.token = token; } /** * Evaluate the filter expression in the given context. * @param context - Evaluation context. */ /** * Return a string representation of the expression. */ } /** * Base class for JSONPath ValueType literals. */ class FilterExpressionLiteral extends FilterExpression {} class NullLiteral extends FilterExpressionLiteral { evaluate() { return null; } toString() { return "null"; } } class BooleanLiteral extends FilterExpressionLiteral { constructor(token, value) { super(token); this.token = token; this.value = value; } evaluate() { return this.value; } toString() { return String(this.value); } } class StringLiteral extends FilterExpressionLiteral { constructor(token, value) { super(token); this.token = token; this.value = value; } evaluate() { return this.value; } toString() { return toCanonical(this.value); } } class NumberLiteral extends FilterExpressionLiteral { constructor(token, value) { super(token); this.token = token; this.value = value; } evaluate() { return this.value; } toString() { return String(this.value); } } class PrefixExpression extends FilterExpression { constructor(token, operator, right) { super(token); this.token = token; this.operator = operator; this.right = right; } evaluate(context) { if (this.operator === "!") { const value = this.right.evaluate(context); if (value instanceof JSONPathNodeList) return value.nodes.length === 0; // negated existence return !isTruthy(value); } throw new JSONPathTypeError(`unknown operator '${this.operator}'`, this.token); } toString(options) { return `${this.operator}${this.right.toString(options)}`; } } const PRECEDENCE_LOGICAL_OR$1 = 4; const PRECEDENCE_LOGICAL_AND$1 = 5; const PRECEDENCE_PREFIX$1 = 7; class InfixExpression extends FilterExpression { constructor(token, left, operator, right) { super(token); this.token = token; this.left = left; this.operator = operator; this.right = right; this.logical = operator === "&&" || operator === "||"; } evaluate(context) { let left = this.left.evaluate(context); if (!this.logical && left instanceof JSONPathNodeList && left.nodes.length === 1) left = left.nodes[0].value; let right = this.right.evaluate(context); if (!this.logical && right instanceof JSONPathNodeList && right.nodes.length === 1) right = right.nodes[0].value; if (this.operator === "&&") { return isTruthy(left) && isTruthy(right); } if (this.operator === "||") { return isTruthy(left) || isTruthy(right); } return compare(left, this.operator, right); } toString(options) { // Note that `LogicalExpression.toString()` does not call this. if (this.logical) { return `(${this.left.toString(options)} ${this.operator} ${this.right.toString(options)})`; } return `${this.left.toString(options)} ${this.operator} ${this.right.toString(options)}`; } } class LogicalExpression extends FilterExpression { constructor(token, expression) { super(token); this.token = token; this.expression = expression; } evaluate(context) { const value = this.expression.evaluate(context); if (value instanceof JSONPathNodeList) return value.nodes.length > 0; // existence return isTruthy(value); } toString(options) { // Minimize parentheses in logical expressions. function _toString(expression, parentPrecedence) { if (expression instanceof InfixExpression) { let precedence; let op; let left; let right; if (expression.operator === "&&") { precedence = PRECEDENCE_LOGICAL_AND$1; op = "&&"; left = _toString(expression.left, precedence); right = _toString(expression.right, precedence); } else if (expression.operator === "||") { precedence = PRECEDENCE_LOGICAL_OR$1; op = "||"; left = _toString(expression.left, precedence); right = _toString(expression.right, precedence); } else { return expression.toString(options); } const expr = `${left} ${op} ${right}`; return precedence < parentPrecedence ? `(${expr})` : expr; } if (expression instanceof PrefixExpression) { const operand = _toString(expression.right, PRECEDENCE_PREFIX$1); const expr = `!${operand}`; return parentPrecedence > PRECEDENCE_PREFIX$1 ? `(${expr})` : expr; } return expression.toString(options); } return _toString(this.expression, 0); } } /** * Base class for relative and absolute JSONPath query expressions. */ class FilterQuery extends FilterExpression { constructor(token, path) { super(token); this.token = token; this.path = path; } } class RelativeQuery extends FilterQuery { evaluate(context) { return context.lazy ? new JSONPathNodeList(Array.from(this.path.lazyQuery(context.currentValue))) : this.path.query(context.currentValue); } toString(options) { return `@${this.path.toString(options).slice(1)}`; } } class RootQuery extends FilterQuery { evaluate(context) { return context.lazy ? new JSONPathNodeList(Array.from(this.path.lazyQuery(context.rootValue))) : this.path.query(context.rootValue); } toString(options) { return this.path.toString(options); } } class FunctionExtension extends FilterExpression { constructor(token, name, args) { super(token); this.token = token; this.name = name; this.args = args; } evaluate(context) { const func = context.environment.functionRegister.get(this.name); if (!func) { throw new UndefinedFilterFunctionError(`filter function '${this.name}' is undefined`, this.token); } const args = this.args.map(arg => arg.evaluate(context)).map((arg, idx) => func.argTypes[idx] !== FunctionExpressionType.NodesType && arg instanceof JSONPathNodeList ? this.unpack_node_list(arg) : arg); return func.call(...args); } toString(options) { return `${this.name}(${this.args.map(e => e.toString(options)).join(", ")})`; } unpack_node_list(arg) { switch (arg.length) { case 0: // If the query results in an empty node list, the argument // is the special result Nothing. return Nothing; case 1: // If the query results in a node list consisting of a single // node, the argument is the value of the node return arg.nodes[0].value; default: return arg; } } } /** * * @param value - */ function isTruthy(value) { if (value instanceof JSONPathNodeList && value.empty()) return false; return !(typeof value === "boolean" && value === false); } function compare(left, operator, right) { switch (operator) { case "==": return eq(left, right); case "!=": return !eq(left, right); case "<": return lt(left, right); case ">": return lt(right, left); case ">=": return lt(right, left) || eq(left, right); case "<=": return lt(left, right) || eq(left, right); default: return false; } } // eslint-disable-next-line sonarjs/cognitive-complexity function eq(left, right) { if (right instanceof JSONPathNodeList) [left, right] = [right, left]; if (left instanceof JSONPathNodeList) { if (right instanceof JSONPathNodeList) { if (left.empty() && right.empty()) return true; if (left.nodes.length === 1 && right.nodes.length === 1) return deepEquals(left.nodes[0].value, right.nodes[0].value); } if (left.empty()) return right === Nothing; if (left.nodes.length === 1) return deepEquals(left.nodes[0].value, right); return false; } if (left === Nothing && right === Nothing) return true; return deepEquals(left, right); } function lt(left, right) { if (isString(left) && isString(right) || isNumber(left) && isNumber(right)) return left < right; return false; } var expression = /*#__PURE__*/Object.freeze({ __proto__: null, BooleanLiteral: BooleanLiteral, FilterExpression: FilterExpression, FilterExpressionLiteral: FilterExpressionLiteral, FilterQuery: FilterQuery, FunctionExtension: FunctionExtension, InfixExpression: InfixExpression, LogicalExpression: LogicalExpression, NullLiteral: NullLiteral, NumberLiteral: NumberLiteral, PrefixExpression: PrefixExpression, RelativeQuery: RelativeQuery, RootQuery: RootQuery, StringLiteral: StringLiteral, compare: compare }); class Count { argTypes = (() => [FunctionExpressionType.NodesType])(); returnType = (() => FunctionExpressionType.ValueType)(); call(nodes) { return nodes.length; } } class Length { argTypes = (() => [FunctionExpressionType.ValueType])(); returnType = (() => FunctionExpressionType.ValueType)(); call(value) { if (isArray(value) || isString(value)) return value.length; if (isObject(value)) return Object.keys(value).length; return Nothing; } } /** * A Least Recently Used cache, implemented as an extended Map. */ class LRUCache extends Map { constructor() { let maxSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 128; let entries = arguments.length > 1 ? arguments[1] : undefined; if (entries !== undefined) { super(entries); } else { super(); } this.maxSize = maxSize; } get(key) { const val = super.get(key); if (this.has(key)) { this.delete(key); this.set(key, val); } return val; } set(key, value) { if (this.has(key)) { this.delete(key); } else if (this.size >= this.maxSize) { const first = this.first(); if (first !== undefined) { this.delete(first); } } return super.set(key, value); } first() { return this.keys().next().value; } } // See https://datatracker.ietf.org/doc/html/rfc9485#name-ecmascript-regexps function mapRegexp(pattern) { let escaped = false; let charClass = false; const parts = []; for (const ch of pattern) { if (escaped) { parts.push(ch); escaped = false; continue; } switch (ch) { case ".": if (!charClass) { parts.push("(?:(?![\r\n])\\P{Cs}|\\p{Cs}\\p{Cs})"); } else { parts.push(ch); } break; case "\\": escaped = true; parts.push(ch); break; case "[": charClass = true; parts.push(ch); break; case "]": charClass = false; parts.push(ch); break; default: parts.push(ch); break; } } return parts.join(""); } function fullMatch(pattern) { const parts = []; const explicitCaret = pattern.startsWith("^"); const explicitDollar = pattern.endsWith("$"); if (!explicitCaret && !explicitDollar) parts.push("^(?:"); parts.push(mapRegexp(pattern)); if (!explicitCaret && !explicitDollar) parts.push(")$"); return parts.join(""); } /* * iregexp-check version 0.1.2 * https://github.com/jg-rp/js-iregexp * * MIT License * * Copyright (c) 2024 James Prior * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ function isNormalChar(c) { return c < "\u0027" || c === "," || c === "-" || c >= "\u002F" && c <= "\u003E" || // / .. > c >= "\u0040" && c <= "\u005A" || // @ .. Z c >= "\u005E" && c <= "\u007A" || // ^ .. z c >= "\u007E" && c <= "\uD7FF" || // skip surrogate code points c >= "\uE000"; } function isCCChar(c) { return c < "\u002C" || c >= "\u002E" && c <= "\u005A" || // '.' .. Z c >= "\u005E" && c <= "\uD7FF" || // skip surrogate code points c >= "\uE000"; } function peg$subclass(child, parent) { function C() { this.constructor = child; } C.prototype = parent.prototype; child.prototype = new C(); } function peg$SyntaxError(message, expected, found, location) { var self = Error.call(this, message); // istanbul ignore next Check is a necessary evil to support older environments if (Object.setPrototypeOf) { Object.setPrototypeOf(self, peg$SyntaxError.prototype); } self.expected = expected; self.found = found; self.location = location; self.name = "SyntaxError"; return self; } peg$subclass(peg$SyntaxError, Error); function peg$padEnd(str, targetLength, padString) { padString = padString || " "; if (str.length > targetLength) { return str; } targetLength -= str.length; padString += padString.repeat(targetLength); return str + padString.slice(0, targetLength); } peg$SyntaxError.prototype.format = function (sources) { var str = "Error: " + this.message; if (this.location) { var src = null; var k; for (k = 0; k < sources.length; k++) { if (sources[k].source === this.location.source) { src = sources[k].text.split(/\r\n|\n|\r/g); break; } } var s = this.location.start; var offset_s = this.location.source && typeof this.location.source.offset === "function" ? this.location.source.offset(s) : s; var loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column; if (src) { var e = this.location.end; var filler = peg$padEnd("", offset_s.line.toString().length, ' '); var line = src[s.line - 1]; var last = s.line === e.line ? e.column : line.length + 1; var hatLen = last - s.column || 1; str += "\n --> " + loc + "\n" + filler + " |\n" + offset_s.line + " | " + line + "\n" + filler + " | " + peg$padEnd("", s.column - 1, ' ') + peg$padEnd("", hatLen, "^"); } else { str += "\n at " + loc; } } return str; }; peg$SyntaxError.buildMessage = function (expected, found) { var DESCRIBE_EXPECTATION_FNS = { literal: function (expectation) { return "\"" + literalEscape(expectation.text) + "\""; }, class: function (expectation) { var escapedParts = expectation.parts.map(function (part) { return Array.isArray(part) ? classEscape(part[0]) + "-" + classEscape(part[1]) : classEscape(part); }); return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]"; }, any: function () { return "any character"; }, end: function () { return "end of input"; }, other: function (expectation) { return expectation.description; } }; function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } function literalEscape(s) { return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, function (ch) { return "\\x0" + hex(ch); }).replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { return "\\x" + hex(ch); }); } function classEscape(s) { return s.replace(/\\/g, "\\\\").replace(/\]/g, "\\]").replace(/\^/g, "\\^").replace(/-/g, "\\-").replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, function (ch) { return "\\x0" + hex(ch); }).replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { return "\\x" + hex(ch); }); } function describeExpectation(expectation) { return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); } function describeExpected(expected) { var descriptions = expected.map(describeExpectation); var i, j; descriptions.sort(); if (descriptions.length > 0) { for (i = 1, j = 1; i < descriptions.length; i++) { if (descriptions[i - 1] !== descriptions[i]) { descriptions[j] = descriptions[i]; j++; } } descriptions.length = j; } switch (descriptions.length) { case 1: return descriptions[0]; case 2: return descriptions[0] + " or " + descriptions[1]; default: return descriptions.slice(0, -1).join(", ") + ", or " + descriptions[descriptions.length - 1]; } } function describeFound(found) { return found ? "\"" + literalEscape(found) + "\"" : "end of input"; } return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; }; function peg$parse(input, options) { options = options !== undefined ? options : {}; var peg$FAILED = {}; var peg$source = options.grammarSource; var peg$startRuleFunctions = { start: peg$parsestart }; var peg$startRuleFunction = peg$parsestart; var peg$c0 = "|"; var peg$c1 = "{"; var peg$c2 = ","; var peg$c3 = "}"; var peg$c4 = "("; var peg$c5 = ")"; var peg$c6 = "."; var peg$c7 = "\\"; var peg$c8 = "["; var peg$c9 = "^"; var peg$c10 = "-"; var peg$c11 = "]"; var peg$c12 = "\\p{"; var peg$c13 = "\\P{"; var peg$c14 = "L"; var peg$c15 = "M"; var peg$c16 = "N"; var peg$c17 = "P"; var peg$c18 = "Z"; var peg$c19 = "S"; var peg$c20 = "C"; var peg$r0 = /^[*-+?]/; var peg$r1 = /^[0-9]/; var peg$r2 = /^[(-+\--.?[-\^nrt{-}]/; var peg$r3 = /^[l-mot-u]/; var peg$r4 = /^[cen]/; var peg$r5 = /^[dlo]/; var peg$r6 = /^[c-fios]/; var peg$r7 = /^[lps]/; var peg$r8 = /^[ckmo]/; var peg$r9 = /^[cfn-o]/; var peg$e0 = peg$literalExpectation("|", false); var peg$e1 = peg$classExpectation([["*", "+"], "?"], false, false); var peg$e2 = peg$literalExpectation("{", false); var peg$e3 = peg$classExpectation([["0", "9"]], false, false); var peg$e4 = peg$literalExpectation(",", false); var peg$e5 = peg$literalExpectation("}", false); var peg$e6 = peg$literalExpectation("(", false); var peg$e7 = peg$literalExpectation(")", false); var peg$e8 = peg$anyExpectation(); var peg$e9 = peg$literalExpectation(".", false); var peg$e10 = peg$literalExpectation("\\", false); var peg$e11 = peg$classExpectation([["(", "+"], ["-", "."], "?", ["[", "^"], "n", "r", "t", ["{", "}"]], false, false); var peg$e12 = peg$literalExpectation("[", false); var peg$e13 = peg$literalExpectation("^", false); var peg$e14 = peg$literalExpectation("-", false); var peg$e15 = peg$literalExpectation("]", false); var peg$e16 = peg$literalExpectation("\\p{", false); var peg$e17 = peg$literalExpectation("\\P{", false); var peg$e18 = peg$literalExpectation("L", false); var peg$e19 = peg$classExpectation([["l", "m"], "o", ["t", "u"]], false, false); var peg$e20 = peg$literalExpectation("M", false); var peg$e21 = peg$classExpectation(["c", "e", "n"], false, false); var peg$e22 = peg$literalExpectation("N", false); var peg$e23 = peg$classExpectation(["d", "l", "o"], false, false); var peg$e24 = peg$literalExpectation("P", false); var peg$e25 = peg$classExpectation([["c", "f"], "i", "o", "s"], false, false); var peg$e26 = peg$literalExpectation("Z", false); var peg$e27 = peg$classExpectation(["l", "p", "s"], false, false); var peg$e28 = peg$literalExpectation("S", false); var peg$e29 = peg$classExpectation(["c", "k", "m", "o"], false, false); var peg$e30 = peg$literalExpectation("C", false); var peg$e31 = peg$classExpectation(["c", "f", ["n", "o"]], false, false); var peg$f0 = function (c) { return isNormalChar(c); }; var peg$f1 = function (c) { return isCCChar(c); }; var peg$currPos = options.peg$currPos | 0; var peg$posDetailsCache = [{ line: 1, column: 1 }]; var peg$maxFailPos = peg$currPos; var peg$maxFailExpected = options.peg$maxFailExpected || []; var peg$silentFails = options.peg$silentFails | 0; var peg$result; if (options.startRule) { if (!(options.startRule in peg$startRuleFunctions)) { throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); } peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; } function peg$literalExpectation(text, ignoreCase) { return { type: "literal", text: text, ignoreCase: ignoreCase }; } function peg$classExpectation(parts, inverted, ignoreCase) { return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; } function peg$anyExpectation() { return { type: "any" }; } function peg$endExpectation() { return { type: "end" }; } function peg$computePosDetails(pos) { var details = peg$posDetailsCache[pos]; var p; if (details) { return details; } else { if (pos >= peg$posDetailsCache.length) { p = peg$posDetailsCache.length - 1; } else { p = pos; while (!peg$posDetailsCache[--p]) {} } details = peg$posDetailsCache[p]; details = { line: details.line, column: details.column }; while (p < pos) { if (input.charCodeAt(p) === 10) { details.line++; details.column = 1; } else { details.column++; } p++; } peg$posDetailsCache[pos] = details; return details; } } function peg$computeLocation(startPos, endPos, offset) { var startPosDetails = peg$computePosDetails(startPos); var endPosDetails = peg$computePosDetails(endPos); var res = { source: peg$source, start: { offset: startPos, line: startPosDetails.line, column: startPosDetails.column }, end: { offset: endPos, line: endPosDetails.line, column: endPosDetails.column } }; return res; } function peg$fail(expected) { if (peg$currPos < peg$maxFailPos) { return; } if (peg$currPos > peg$maxFailPos) { peg$maxFailPos = peg$currPos; peg$maxFailExpected = []; } peg$maxFailExpected.push(expected); } function peg$buildStructuredError(expected, found, location) { return new peg$SyntaxError(peg$SyntaxError.buildMessage(expected, found), expected, found, location); } function peg$parsestart() { var s0; s0 = peg$parseiregexp(); return s0; } function peg$parseiregexp() { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; s1 = peg$parsebranch(); s2 = []; s3 = peg$currPos; if (input.charCodeAt(peg$currPos) === 124) { s4 = peg$c0; peg$currPos++; } else { s4 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e0); } } if (s4 !== peg$FAILED) { s5 = peg$parsebranch(); s4 = [s4, s5]; s3 = s4; } else { peg$currPos = s3; s3 = peg$FAILED; } while (s3 !== peg$FAILED) { s2.push(s3); s3 = peg$currPos; if (input.charCodeAt(peg$currPos) === 124) { s4 = peg$c0; peg$currPos++; } else { s4 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e0); } } if (s4 !== peg$FAILED) { s5 = peg$parsebranch(); s4 = [s4, s5]; s3 = s4; } else { peg$currPos = s3; s3 = peg$FAILED; } } s1 = [s1, s2]; s0 = s1; return s0; } function peg$parsebranch() { var s0, s1; s0 = []; s1 = peg$parsepiece(); while (s1 !== peg$FAILED) { s0.push(s1); s1 = peg$parsepiece(); } return s0; } function peg$parsepiece() { var s0, s1, s2; s0 = peg$currPos; s1 = peg$parseatom(); if (s1 !== peg$FAILED) { s2 = peg$parsequantifier(); if (s2 === peg$FAILED) { s2 = null; } s1 = [s1, s2]; s0 = s1; } else { peg$currPos = s0; s0 = peg$FAILED; } return s0; } function peg$parsequantifier() { var s0; s0 = input.charAt(peg$currPos); if (peg$r0.test(s0)) { peg$currPos++; } else { s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e1); } } if (s0 === peg$FAILED) { s0 = peg$parserange_quantifier(); } return s0; } function peg$parserange_quantifier() { var s0, s1, s2, s3, s4, s5, s6; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 123) { s1 = peg$c1; peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e2); } } if (s1 !== peg$FAILED) { s2 = []; s3 = input.charAt(peg$currPos); if (peg$r1.test(s3)) { peg$currPos++; } else { s3 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e3); } } if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { s2.push(s3); s3 = input.charAt(peg$currPos); if (peg$r1.test(s3)) { peg$currPos++; } else { s3 = peg$FAILED;