json-p3
Version:
JSONPath, JSON Pointer and JSON Patch
1,715 lines (1,634 loc) • 155 kB
JavaScript
/*
* 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.
*
*/
/**
* 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;
if (peg$s