UNPKG

@astronautlabs/jsonpath

Version:

Query JavaScript objects with JSONPath expressions. Robust / safe JSONPath engine for Node.js.

319 lines (257 loc) 8.65 kB
import * as aesprim from './esprima'; import type * as ESTree from 'estree'; import { slice } from './slice'; import { JSONPath } from '.'; import _evaluate from 'static-eval'; /** * @internal * @hidden */ function traverser(recurse?) { return function(partial, ref, passable, count) { var value = partial.value; var path = partial.path; var results = []; var descend = function(value, path) { if (is_array(value)) { value.forEach(function(element, index) { if (results.length >= count) { return } if (passable(index, element, ref)) { results.push({ path: path.concat(index), value: element }); } }); value.forEach(function(element, index) { if (results.length >= count) { return } if (recurse) { descend(element, path.concat(index)); } }); } else if (is_object(value)) { Object.keys(value).forEach(function(k) { if (results.length >= count) { return } if (passable(k, value[k], ref)) { results.push({ path: path.concat(k), value: value[k] }); } }) Object.keys(value).forEach(function(k) { if (results.length >= count) { return } if (recurse) { descend(value[k], path.concat(k)); } }); } }.bind(this); descend(value, path); return results; } } /** * @internal * @hidden */ function evaluate(ast: ESTree.Expression, vars: Record<string,any>) { try { return _evaluate(ast, vars); } catch (e) { } } /** * @internal * @hidden */ function _descend(passable) { return function(component, partial, count) { return this.descend(partial, component.expression.value, passable, count); } } /** * @internal * @hidden */ function _traverse(passable) { return function(component, partial, count) { return this.traverse(partial, component.expression.value, passable, count); } } /** * @internal * @hidden */ function unique(results : any[]) { results = results.filter(d => d); return uniq( results, r => r.path.map(function(c) { return String(c).replace('-', '--') }).join('-') ); } /** * @internal * @hidden */ function _parse_nullable_int(val) { var sval = String(val); return sval.match(/^-?[0-9]+$/) ? parseInt(sval) : null; } /** * @internal * @hidden */ function is_array(val) { return Array.isArray(val); } /** * @internal * @hidden */ function is_object(val) { // is this a non-array, non-null object? return val && !(val instanceof Array) && val instanceof Object; } /** * @internal * @hidden */ function eval_recurse(partial, src, template) { var ast = aesprim.parse(src).body[0].expression; var value = evaluate(ast, { '@': partial.value }); var path = template.replace(/\{\{\s*value\s*\}\}/g, value); var results = JSONPath.nodes(partial.value, path); results.forEach(function(r) { r.path = partial.path.concat(r.path.slice(1)); }); return results; } /** * @internal * @hidden */ export class Handlers { constructor() { this.initialize(); } traverse; descend; private _fns = { 'member-child-identifier': (component, partial) => { var key = component.expression.value; var value = partial.value; if (value instanceof Object && key in value) { return [ { value: value[key], path: partial.path.concat(key) } ] } }, 'member-descendant-identifier': _traverse(function(key, value, ref) { return key == ref }), 'subscript-child-numeric_literal': _descend(function(key, value, ref) { return key === ref }), 'member-child-numeric_literal': _descend(function(key, value, ref) { return String(key) === String(ref) }), 'subscript-descendant-numeric_literal': _traverse(function(key, value, ref) { return key === ref }), 'member-child-wildcard': _descend(function() { return true }), 'member-descendant-wildcard': _traverse(function() { return true }), 'subscript-descendant-wildcard': _traverse(function() { return true }), 'subscript-child-wildcard': _descend(function() { return true }), 'subscript-child-slice': function(component, partial) { if (is_array(partial.value)) { var args = component.expression.value.split(':').map(_parse_nullable_int); var values = partial.value.map(function(v, i) { return { value: v, path: partial.path.concat(i) } }); return slice.apply(null, [values].concat(args)); } }, 'subscript-child-union': function(component, partial) { var results = []; component.expression.value.forEach(function(component) { var _component = { operation: 'subscript', scope: 'child', expression: component.expression }; var handler = this.resolve(_component); var _results = handler(_component, partial); if (_results) { results = results.concat(_results); } }, this); return unique(results); }, 'subscript-descendant-union': function(component, partial, count) { var self = this; var results = []; var nodes = JSONPath.nodes(partial, '$..*').slice(1); nodes.forEach(function(node) { if (results.length >= count) return; component.expression.value.forEach(function(component) { var _component = { operation: 'subscript', scope: 'child', expression: component.expression }; var handler = self.resolve(_component); var _results = handler(_component, node); results = results.concat(_results); }); }); return unique(results); }, 'subscript-child-filter_expression': function(component, partial, count) { // slice out the expression from ?(expression) var src = component.expression.value.slice(2, -1); var ast = aesprim.parse(src).body[0].expression; var passable = function(key, value) { return evaluate(ast, { '@': value }); } return this.descend(partial, null, passable, count); }, 'subscript-descendant-filter_expression': function(component, partial, count) { // slice out the expression from ?(expression) var src = component.expression.value.slice(2, -1); var ast = aesprim.parse(src).body[0].expression; var passable = function(key, value) { return evaluate(ast, { '@': value }); } return this.traverse(partial, null, passable, count); }, 'subscript-child-script_expression': function(component, partial) { var exp = component.expression.value.slice(1, -1); return eval_recurse(partial, exp, '$[{{value}}]'); }, 'member-child-script_expression': function(component, partial) { var exp = component.expression.value.slice(1, -1); return eval_recurse(partial, exp, '$.{{value}}'); }, 'member-descendant-script_expression': function(component, partial) { var exp = component.expression.value.slice(1, -1); return eval_recurse(partial, exp, '$..value'); } }; private initialize() { this.traverse = traverser(true); this.descend = traverser(); this._fns['subscript-child-string_literal'] = this._fns['member-child-identifier']; this._fns['member-descendant-numeric_literal'] = this._fns['subscript-descendant-string_literal'] = this._fns['member-descendant-identifier']; } resolve(component) { var key = [ component.operation, component.scope, component.expression.type ].join('-'); var method = this._fns[key]; if (!method) throw new Error("couldn't resolve key: " + key); return method.bind(this); } register(key, handler) { if (!(handler instanceof Function)) { throw new Error("handler must be a function"); } this._fns[key] = handler; } } function uniq<T>(array: T[], iteratee: (t: T, i: number) => any) { var result = []; var seen = []; for (var i = 0, length = array?.length; i < length; i++) { var value = array[i], computed = iteratee ? iteratee(value, i) : value; if (!seen.includes(computed)) { seen.push(computed); result.push(value); } } return result; }