├── .nojekyll ├── pnpm-workspace.yaml ├── .babelrc.json ├── .github ├── FUNDING.yml ├── pull_request_template.md ├── workflows │ └── node.js.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── licenseInfo.json ├── .mocharc.cjs ├── .gitignore ├── docs └── ts │ ├── .nojekyll │ ├── assets │ ├── hierarchy.js │ ├── navigation.js │ ├── search.js │ ├── highlight.css │ ├── icons.svg │ └── icons.js │ ├── hierarchy.html │ ├── types │ ├── JSONPathType.html │ ├── JSONPathOtherTypeCallback.html │ └── JSONPathCallback.html │ ├── classes │ └── EvalClass.html │ └── interfaces │ └── JSONPathCallable.html ├── src ├── jsonpath-node.js ├── jsonpath-browser.js ├── Safe-Script.js └── jsonpath.d.ts ├── tsconfig.json ├── mocha-multi-reporters.json ├── demo ├── types.d.ts ├── index.css ├── node-import-test.js ├── index.html └── index.js ├── .editorconfig ├── test-helpers ├── node-env.js └── checkVM.js ├── .npmignore ├── test ├── test.custom-properties.js ├── test.toPointer.js ├── test.cli.js ├── test.performance.js ├── test.escaping.js ├── test.return.js ├── test.slice.js ├── index.html ├── test.at_and_dollar.js ├── test.arr.js ├── test.intermixed.arr.js ├── test.pointer.js ├── test.toPath.js ├── test.all.js ├── test.properties.js ├── test.api.js ├── test.path_expressions.js ├── test.errors.js ├── test.parent-selector.js ├── test.callback.js ├── test.type-operators.js ├── test.nested_expression.js ├── test.eval.js └── test.examples.js ├── badges ├── tests-badge.svg ├── licenses-badge.svg ├── coverage-badge.svg └── licenses-badge-dev.svg ├── bin └── jsonpath-cli.js ├── LICENSE ├── SECURITY.md ├── eslint.config.js ├── rollup.config.js └── package.json /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | ignoredBuiltDependencies: 2 | - unrs-resolver 3 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [brettz9] # May have up to 4 comma-separated user names 2 | -------------------------------------------------------------------------------- /licenseInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "bundledRootPackages": [], 3 | "filesByLicense": {} 4 | } 5 | -------------------------------------------------------------------------------- /.mocharc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | reporter: 'mocha-multi-reporters' 5 | }; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .DS_Store 3 | .idea 4 | ignore 5 | temp 6 | node_modules 7 | pids 8 | reports 9 | target 10 | *.log 11 | coverage 12 | -------------------------------------------------------------------------------- /docs/ts/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /src/jsonpath-node.js: -------------------------------------------------------------------------------- 1 | import vm from 'vm'; 2 | import {JSONPath} from './jsonpath.js'; 3 | 4 | JSONPath.prototype.vm = vm; 5 | 6 | export { 7 | JSONPath 8 | }; 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2015"] 4 | }, 5 | "include": [ 6 | "src" 7 | ], 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /mocha-multi-reporters.json: -------------------------------------------------------------------------------- 1 | { 2 | "mochaBadgeGeneratorReporterOptions": { 3 | "badge_output": "./badges/tests-badge.svg" 4 | }, 5 | "reporterEnabled": "mocha-badge-generator, spec" 6 | } 7 | -------------------------------------------------------------------------------- /docs/ts/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJyVjrEKgzAYhN/l5thipSL/1rVDLTiKQ4iRhMakJL+T5N2LFBwKhXa55buPuxUxBE6gvmwGgagnpxXb4BNoRdls6eWsQbh27e0u2bTPNxd4WD+CTudaYIkOBOtZx0kqnY4f7YPh2UFAOZkSCJzGYtOLXdmgsW6M2oP6qh6yQFV/378sHDqWkf87sms/PMo5vwAOBWG1" -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## PR description 2 | 3 | 4 | 5 | 6 | ## Checklist 7 | 8 | - [ ] - Added tests 9 | - [ ] - Ran `npm test`, ensuring linting passes 10 | - [ ] - Adjust README documentation if relevant 11 | -------------------------------------------------------------------------------- /docs/ts/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "eJyV0MEKgkAQBuB3mbNUSlp5i+jSIQO7RYfRVhQ3FR0jCd89NmRN09qOy//Ptzt7egCxO4EN2xvyDceiAA0ypBBs8MWRFVMZTUK6ctAgjpIL2LqxrDU5v3Od/QEpHDE6sZKDnKPHWUtFCbE8QP9da0pd0DCtAdDJKEqT4qvXdP7g1iWlLmFOKq4sq1wgdvPQj1uYqqy3u8h71my10E1j6MEUsvxYZewX/FFUvUHMjKEiU3VaIygT//Vz0uka1rw+PwFd7+wg" -------------------------------------------------------------------------------- /demo/types.d.ts: -------------------------------------------------------------------------------- 1 | import '../src/jsonpath.d.ts' 2 | import type { JSONPathType } from 'jsonpath-plus'; 3 | 4 | declare global { 5 | var LZString: { 6 | decompressFromEncodedURIComponent: (value: string) => string; 7 | compressToEncodedURIComponent: (value: string) => string; 8 | }; 9 | var JSONPath: { 10 | JSONPath: JSONPathType 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig file: https://EditorConfig.org 2 | ; Install the "EditorConfig" plugin into your editor to use 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | ; indent_style = tab 12 | indent_size = 4 13 | trim_trailing_whitespace = true 14 | 15 | [*.{json,yml}] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /test-helpers/node-env.js: -------------------------------------------------------------------------------- 1 | import {assert, expect} from 'chai'; 2 | import {JSONPath} from '../src/jsonpath-node.js'; 3 | import { 4 | JSONPath as JSONPathBrowser 5 | } from '../src/jsonpath-browser.js'; 6 | 7 | globalThis.assert = assert; 8 | globalThis.expect = expect; 9 | 10 | globalThis.jsonpathNodeVM = JSONPath; 11 | globalThis.jsonpath = JSONPath; 12 | globalThis.jsonpathBrowser = JSONPathBrowser; 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test 3 | test-helpers 4 | rollup.config.js 5 | lgtm.yml 6 | .travis.yml 7 | .DS_Store 8 | .babelrc.json 9 | .idea 10 | docs 11 | .nyc_output 12 | coverage 13 | badges 14 | mocha-multi-reporters.json 15 | .mocharc.cjs 16 | .github 17 | .nojekyll 18 | ignore 19 | pnpm-lock.yaml 20 | eslint.config.js 21 | .editorconfig 22 | .eslintignore 23 | licenseInfo.json 24 | tsconfig.json 25 | demo 26 | -------------------------------------------------------------------------------- /demo/index.css: -------------------------------------------------------------------------------- 1 | .row { 2 | display: flex; 3 | gap: 8px; 4 | margin-bottom: 10px; 5 | } 6 | 7 | .grow, #jsonpath { 8 | flex-grow: 1; 9 | } 10 | 11 | .row label { 12 | display: flex; 13 | } 14 | 15 | .container { 16 | float: left; 17 | width: 48%; 18 | } 19 | 20 | .container textarea { 21 | margin: 2%; 22 | width: 98%; 23 | height: 565px; 24 | } 25 | 26 | #demoNode { 27 | font-size: small; 28 | } 29 | -------------------------------------------------------------------------------- /test/test.custom-properties.js: -------------------------------------------------------------------------------- 1 | import {checkBuiltInVMAndNodeVM} from '../test-helpers/checkVM.js'; 2 | 3 | checkBuiltInVMAndNodeVM(function (vmType, setBuiltInState) { 4 | describe(`JSONPath - Custom properties (${vmType})`, function () { 5 | before(setBuiltInState); 6 | 7 | const t1 = { 8 | b: {true: 'abc', false: 'def'}, 9 | c: {true: 'qrs', false: 'tuv'} 10 | }; 11 | 12 | it('@path for index', () => { 13 | const result = jsonpath({json: t1, path: '$.*[(@path === "$[\'b\']")]', wrap: false}); 14 | assert.deepEqual(result, ['abc', 'tuv']); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: 15 | - 12.x 16 | - 14.x 17 | - 16.x 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Setup PNPM 25 | uses: pnpm/action-setup@v1.2.1 26 | with: 27 | version: 4.11.1 28 | - run: pnpm i 29 | - run: pnpm run build --if-present 30 | - run: pnpm test 31 | -------------------------------------------------------------------------------- /badges/tests-badge.svg: -------------------------------------------------------------------------------- 1 | TestsTests277/277277/277 -------------------------------------------------------------------------------- /test/test.toPointer.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - toPointer', function () { 3 | it('toPointer', () => { 4 | const expected = '/store/bicycle/color'; 5 | const result = jsonpath.toPointer(['$', 'store', 'bicycle', 'color']); 6 | assert.deepEqual(result, expected); 7 | }); 8 | it('toPointer (stripped)', () => { 9 | const expected = '/store/bicycle/color'; 10 | let result = jsonpath.toPointer(['$', 'store', 'bicycle', 'color', '^']); 11 | assert.deepEqual(result, expected); 12 | result = jsonpath.toPointer(['$', 'store', 'bicycle', 'color', '@string()']); 13 | assert.deepEqual(result, expected); 14 | result = jsonpath.toPointer(['$', 'store', 'bicycle', 'color', '~']); 15 | assert.deepEqual(result, expected); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/test.cli.js: -------------------------------------------------------------------------------- 1 | import {promisify} from "util"; 2 | import {exec as _exec} from "child_process"; 3 | import path from "path"; 4 | 5 | const exec = promisify(_exec); 6 | 7 | describe("JSONPath - cli", () => { 8 | it("with filePath and jsonPath", async () => { 9 | const out = await exec("bin/jsonpath-cli.js package.json name"); 10 | expect(out.stdout).to.equal("[ 'jsonpath-plus' ]\n"); 11 | }); 12 | 13 | it("invalid arguments", async () => { 14 | const binPath = path.resolve("bin/jsonpath-cli.js"); 15 | let out; 16 | try { 17 | out = await exec("bin/jsonpath-cli.js wrong-file.json"); 18 | } catch (err) { 19 | out = err; 20 | } 21 | expect(out).to.have.property("code", 1); 22 | expect(out).to.have.property("stderr"); 23 | expect(out.stderr).to.include(`usage: ${binPath} \n\n`); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /bin/jsonpath-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {readFile} from 'fs/promises'; 3 | import {JSONPath as jsonpath} from '../dist/index-node-esm.js'; 4 | 5 | const file = process.argv[2]; 6 | const path = process.argv[3]; 7 | 8 | try { 9 | const json = JSON.parse(await readFile(file, 'utf8')); 10 | runQuery(json, path); 11 | } catch (e) { 12 | /* eslint-disable no-console -- CLI */ 13 | console.error(`usage: ${process.argv[1]} \n`); 14 | console.error(e); 15 | /* eslint-enable no-console -- CLI */ 16 | process.exit(1); 17 | } 18 | 19 | /** 20 | * @typedef {any} JSON 21 | */ 22 | 23 | /** 24 | * @param {JSON} json 25 | * @param {string} pth 26 | * @returns {void} 27 | */ 28 | function runQuery (json, pth) { 29 | const result = jsonpath({ 30 | json, 31 | path: pth 32 | }); 33 | 34 | // eslint-disable-next-line no-console -- CLI 35 | console.log(result); 36 | } 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Report a new feature 4 | title: '' 5 | labels: Feature 6 | assignees: '' 7 | --- 8 | 9 | 20 | 21 | ## Motivation 22 | 23 | 24 | 25 | ## Current behavior 26 | 27 | 29 | 30 | ## Desired behavior 31 | 32 | 33 | 34 | ## Alternatives considered 35 | 36 | 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report in case we may be able to help 4 | labels: Bug - unconfirmed 5 | 6 | --- 7 | 12 | 13 | ## Describe the bug 14 | 15 | 16 | 17 | ## Code sample or steps to reproduce 18 | 19 | ```js 20 | // Code that reproduces problem here 21 | ``` 22 | 23 | ### Console error or logs 24 | 25 | ## Expected behavior 26 | 27 | 28 | 29 | ## Expected result 30 | 31 | ```json 32 | 33 | ``` 34 | 35 | ## Environment (IMPORTANT) 36 | - JSONPath-Plus version: [e.g. 4.0.0] 37 | 38 | ## Desktop** 39 | - OS: [e.g. Windows] 40 | - Browser and version [e.g. chrome 65] or Node Version [e.g. 10.2] 41 | 42 | ## Additional context 43 | 44 | 45 | -------------------------------------------------------------------------------- /badges/licenses-badge.svg: -------------------------------------------------------------------------------- 1 | License typesLicense types(project, deps, and bundled devDeps)(project, deps, and bundled devDeps)PermissivePermissive1. MIT1. MIT -------------------------------------------------------------------------------- /badges/coverage-badge.svg: -------------------------------------------------------------------------------- 1 | Statements 100%Statements 100%Branches 100%Branches 100%Lines 100%Lines 100%Functions 100%Functions 100% 2 | -------------------------------------------------------------------------------- /test-helpers/checkVM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @callback BeforeChecker 3 | * @returns {void} 4 | */ 5 | 6 | /** 7 | * @callback VMTestIterator 8 | * @param {"Node vm"|"JSONPath vm"} vmType 9 | * @param {BeforeChecker} beforeChecker 10 | * @returns {void} 11 | */ 12 | 13 | /** 14 | * @param {VMTestIterator} cb 15 | * @returns {void} 16 | */ 17 | function checkBuiltInVMAndNodeVM (cb) { 18 | if (typeof process === 'undefined') { 19 | // eslint-disable-next-line n/no-callback-literal -- Convenient 20 | cb('JSONPath vm', () => { 21 | // 22 | }); 23 | return; 24 | } 25 | [ 26 | 'Node vm', 27 | 'JSONPath vm' 28 | ].forEach((vmType) => { 29 | const checkingBrowserVM = vmType === 'JSONPath vm'; 30 | cb( 31 | vmType, 32 | checkingBrowserVM 33 | ? () => { 34 | globalThis.jsonpath = globalThis.jsonpathBrowser; 35 | } 36 | : () => { 37 | globalThis.jsonpath = globalThis.jsonpathNodeVM; 38 | } 39 | ); 40 | }); 41 | } 42 | 43 | export {checkBuiltInVMAndNodeVM}; 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-2019 Stefan Goessner, Subbu Allamaraju, Mike Brevoort, 4 | Robert Krahn, Brett Zamir, Richard Schneider 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | **Please do not report security vulnerabilities through public GitHub issues.** 6 | 7 | If you believe you’ve found a security vulnerability, please send it to us by emailing [brettz9@yahoo.com](mailto:brettz9@yahoo.com). Please include the following details with your report: 8 | 9 | 1. Description of the location and potential impact of the vulnerability 10 | 11 | 2. A detailed description of the steps required to reproduce the vulnerability (POC scripts, etc.). 12 | 13 | 3. How you would like to be credited. 14 | 15 | We will evaluate the vulnerability and, if necessary, release a fix or unertake mitigating steps to address it. We will contact you to let you know the outcome, and will credit you in the report. 16 | 17 | Please **do not disclose the vulnerability publicly** until we have sufficient time to release a fix. 18 | 19 | Once we have either a) published a fix, b) declined to address the vulnerability for whatever reason, or c) taken more than 30 days to reply, we welcome you to publicly report the vulnerability on our tracker and disclose it publicly. If you intend to 20 | disclose sooner regardless of our requested policy, please at least indicate to us when you plan to disclose. 21 | -------------------------------------------------------------------------------- /test/test.performance.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - Performance', function () { 3 | this.timeout(5000); 4 | const arraySize = 12333, 5 | resultCount = 1150, 6 | itemCount = 150, 7 | groupCount = 245; 8 | 9 | const json = { 10 | results: [] 11 | }; 12 | 13 | let i, j; 14 | 15 | const bigArray = []; 16 | for (i = 0; i < arraySize; i++) { 17 | bigArray[i] = 1; 18 | } 19 | 20 | const items = []; 21 | for (i = 0; i < itemCount; i++) { 22 | // eslint-disable-next-line unicorn/prefer-structured-clone -- Want JSON 23 | items[i] = JSON.parse(JSON.stringify({a: {b: 0, c: 0}, s: {b: {c: bigArray}}})); 24 | } 25 | 26 | for (i = 0; i < resultCount; i++) { 27 | json.results[i] = {groups: [], v: {v: [1, 2, 3, 4, 5, 6, 7, 8]}}; 28 | json.results[i].groups = []; 29 | for (j = 0; j < groupCount; j++) { 30 | json.results[i].groups[j] = {items, a: "121212"}; 31 | } 32 | } 33 | 34 | it('performance', () => { 35 | const expectedDuration = typeof globalThis !== 'undefined' ? 4500 : 2500; 36 | const start = Date.now(); 37 | jsonpath({json, path: '$.results[*].groups[*].items[42]'}); 38 | assert.strictEqual((Date.now() - start) < expectedDuration, true); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/test.escaping.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - Escaping', function () { 3 | const json = { 4 | '*': 'star', 5 | 'rest': 'rest', 6 | 'foo': 'bar' 7 | }; 8 | 9 | const jsonMissingSpecial = { 10 | 'rest': 'rest', 11 | 'foo': 'bar' 12 | }; 13 | 14 | it('escape *', () => { 15 | let expected = ['star']; 16 | let result = jsonpath({json, path: "$['`*']"}); 17 | assert.deepEqual(result, expected); 18 | 19 | expected = []; 20 | result = jsonpath({json: jsonMissingSpecial, path: "$['`*']"}); 21 | assert.deepEqual(result, expected); 22 | 23 | expected = ['star', 'rest']; 24 | result = jsonpath({json, path: "$[`*,rest]"}); 25 | assert.deepEqual(result, expected); 26 | 27 | expected = ['star']; 28 | result = jsonpath({json, path: "$.`*"}); 29 | assert.deepEqual(result, expected); 30 | 31 | expected = []; 32 | result = jsonpath({json: jsonMissingSpecial, path: "$.`*"}); 33 | assert.deepEqual(result, expected); 34 | 35 | expected = ['star', 'rest', 'bar']; 36 | result = jsonpath({json, path: "$['*']"}); 37 | assert.deepEqual(result, expected); 38 | 39 | expected = ['rest', 'bar']; 40 | result = jsonpath({json: jsonMissingSpecial, path: "$['*']"}); 41 | assert.deepEqual(result, expected); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /demo/node-import-test.js: -------------------------------------------------------------------------------- 1 | import {JSONPath as jsonpath} from '../dist/index-node-esm.js'; 2 | 3 | /* eslint-disable @stylistic/quotes, @stylistic/quote-props -- Convenient */ 4 | const json = { 5 | "store": { 6 | "book": [{ 7 | "category": "reference", 8 | "author": "Nigel Rees", 9 | "title": "Sayings of the Century", 10 | "price": 8.95 11 | }, 12 | { 13 | "category": "fiction", 14 | "author": "Evelyn Waugh", 15 | "title": "Sword of Honour", 16 | "price": 12.99 17 | }, 18 | { 19 | "category": "fiction", 20 | "author": "Herman Melville", 21 | "title": "Moby Dick", 22 | "isbn": "0-553-21311-3", 23 | "price": 8.99 24 | }, 25 | { 26 | "category": "fiction", 27 | "author": "J. R. R. Tolkien", 28 | "title": "The Lord of the Rings", 29 | "isbn": "0-395-19395-8", 30 | "price": 22.99 31 | }], 32 | "bicycle": { 33 | "color": "red", 34 | "price": 19.95 35 | } 36 | } 37 | }; 38 | /* eslint-enable @stylistic/quotes, @stylistic/quote-props -- Convenient */ 39 | 40 | const result = jsonpath({ 41 | json, 42 | path: '$.store.book[*].author' 43 | }); 44 | 45 | // eslint-disable-next-line no-console -- Testing 46 | console.log('result', result); 47 | -------------------------------------------------------------------------------- /test/test.return.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - Return', function () { 3 | const json = {"store": { 4 | "book": [ 5 | { 6 | "category": "reference", 7 | "author": "Nigel Rees", 8 | "title": "Sayings of the Century", 9 | "price": 8.95 10 | }, 11 | { 12 | "category": "fiction", 13 | "author": "Evelyn Waugh", 14 | "title": "Sword of Honour", 15 | "price": 12.99 16 | }, 17 | { 18 | "category": "fiction", 19 | "author": "Herman Melville", 20 | "title": "Moby Dick", 21 | "isbn": "0-553-21311-3", 22 | "price": 8.99 23 | }, 24 | { 25 | "category": "fiction", 26 | "author": "J. R. R. Tolkien", 27 | "title": "The Lord of the Rings", 28 | "isbn": "0-395-19395-8", 29 | "price": 22.99 30 | } 31 | ], 32 | "bicycle": { 33 | "color": "red", 34 | "price": 19.95 35 | } 36 | }}; 37 | 38 | it('single result: path payload', () => { 39 | const expected = "$['store']['bicycle']['color']"; 40 | const result = jsonpath({json, path: "$.store.bicycle.color", resultType: 'path', wrap: false}); 41 | assert.deepEqual(result, expected); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/test.slice.js: -------------------------------------------------------------------------------- 1 | describe('JSONPath - slice', function () { 2 | const json = { 3 | "name": "root", 4 | "children": {} 5 | }; 6 | it('should return empty array if slicing non-array', function () { 7 | const expected = undefined; 8 | const result = jsonpath({json, path: '$.children[1:3]', wrap: false}); 9 | assert.deepEqual(result, expected); 10 | }); 11 | 12 | it('should return objects with slice step', function () { 13 | const jsonWithChildren = { 14 | "name": "root", 15 | "children": [ 16 | {a: 1}, {a: 2}, {a: 3}, {a: 4}, {a: 5}, {a: 6} 17 | ] 18 | }; 19 | const expected = [ 20 | {a: 2}, {a: 4}, {a: 6} 21 | ]; 22 | const result = jsonpath({ 23 | json: jsonWithChildren, 24 | path: '$.children[1:6:2]' 25 | }); 26 | assert.deepEqual(result, expected); 27 | }); 28 | 29 | it('should return objects with negative end slice', function () { 30 | const jsonWithChildren = { 31 | "name": "root", 32 | "children": [ 33 | {a: 1}, {a: 2}, {a: 3}, {a: 4}, {a: 5}, {a: 6} 34 | ] 35 | }; 36 | const expected = [ 37 | {a: 2}, {a: 3} 38 | ]; 39 | const result = jsonpath({ 40 | json: jsonWithChildren, 41 | path: '$.children[1:-3]' 42 | }); 43 | assert.deepEqual(result, expected); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSONPath Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | JSONPath Tests 14 | 15 | 16 | 27 | 28 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/test.at_and_dollar.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - At and Dollar sign', function () { 3 | const t1 = { 4 | simpleString: "simpleString", 5 | "@": "@asPropertyName", 6 | "a$a": "$inPropertyName", 7 | "$": { 8 | "@": "withboth" 9 | }, 10 | a: { 11 | b: { 12 | c: "food" 13 | } 14 | } 15 | }; 16 | 17 | it('test undefined, null', () => { 18 | assert.strictEqual(jsonpath({json: {a: null}, path: '$.a', wrap: false}), null); 19 | assert.strictEqual(jsonpath({json: undefined, path: 'foo'}), undefined); 20 | assert.strictEqual(jsonpath({json: null, path: 'foo'}), undefined); 21 | assert.strictEqual(jsonpath({json: {}, path: 'foo'})[0], undefined); 22 | assert.strictEqual(jsonpath({json: {a: 'b'}, path: 'foo'})[0], undefined); 23 | assert.strictEqual(jsonpath({json: {a: 'b'}, path: 'foo'})[100], undefined); 24 | }); 25 | 26 | it('test $ and @', () => { 27 | assert.strictEqual(jsonpath({json: t1, path: '`$'})[0], t1.$); 28 | assert.strictEqual(jsonpath({json: t1, path: 'a$a'})[0], t1.a$a); 29 | assert.strictEqual(jsonpath({json: t1, path: '`@'})[0], t1['@']); 30 | assert.strictEqual(jsonpath({json: t1, path: '$.`$.`@'})[0], t1.$['@']); 31 | assert.strictEqual(jsonpath({json: t1, path: String.raw`\@`})[1], undefined); 32 | }); 33 | 34 | it('@ as false', () => { 35 | const json = { 36 | a: { 37 | b: false 38 | } 39 | }; 40 | const expected = [false]; 41 | const result = jsonpath({json, path: "$..*[?(@ === false)]", wrap: false}); 42 | assert.deepEqual(result, expected); 43 | }); 44 | 45 | it('@ as 0', function () { 46 | const json = { 47 | a: { 48 | b: 0 49 | } 50 | }; 51 | const expected = [0]; 52 | const result = jsonpath({json, path: "$.a[?(@property === 'b' && @ < 1)]", wrap: false}); 53 | assert.deepEqual(result, expected); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/test.arr.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - Array', function () { 3 | const json = { 4 | "store": { 5 | "book": { 6 | "category": "reference", 7 | "author": "Nigel Rees", 8 | "title": "Sayings of the Century", 9 | "price": [8.95, 8.94, 8.93] 10 | }, 11 | "books": [{ 12 | "category": "reference", 13 | "author": "Nigel Rees", 14 | "title": "Sayings of the Century", 15 | "price": [8.95, 8.94, 8.93] 16 | }] 17 | } 18 | }; 19 | 20 | it('get single', () => { 21 | const expected = json.store.book; 22 | const result = jsonpath({json, path: 'store.book', flatten: true, wrap: false}); 23 | assert.deepEqual(result, expected); 24 | }); 25 | 26 | it('get arr', () => { 27 | const expected = json.store.books; 28 | const result = jsonpath({json, path: 'store.books', flatten: true, wrap: false}); 29 | assert.deepEqual(result, expected); 30 | }); 31 | 32 | it('query single element arr w/scalar value', () => { 33 | const expected = [json.store.books[0].author]; 34 | const result = jsonpath({json, path: 'store.books[*].author', wrap: false}); 35 | assert.deepEqual(result, expected); 36 | }); 37 | 38 | it('query single element arr w/array value', () => { 39 | const authors = ['Dickens', 'Lancaster']; 40 | const input = { 41 | books: [{authors}] 42 | }; 43 | const expected = authors; 44 | const result = jsonpath({json: input, path: '$.books[0].authors', wrap: false}); 45 | assert.deepEqual(result, expected); 46 | }); 47 | 48 | it('query multi element arr w/array value', () => { 49 | const authors = ['Dickens', 'Lancaster']; 50 | const input = { 51 | books: [{authors}, {authors}] 52 | }; 53 | const expected = [authors, authors]; 54 | const result = jsonpath({json: input, path: '$.books[*].authors', wrap: false}); 55 | assert.deepEqual(result, expected); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /docs/ts/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "eJytmk1v2zgQhv8Le1VTc0j561YUPXQPbYEs9iIEC8VWGm0dyZDofCDIf1/QoiWOhnJGlU8tLM47r8lnODSVV1GVT7VYJ6/id15sxRpmq4WMIRJF+pCJtfjr+sf3n6m5/5Ludrfp5reIxKHaibUwL/us/tR/fHVvHnYiEptdWtdZLdZCvEXvaf8w91n198s+eycJGXc2m4Rlm+nrY7r7Yge1yi7kU/vkrFYsO9ebsqhNddiYsnpH7QMe6ilHYp9WWWGQNX+idOe9OhTfiu/Z05eyMNmzeS9ndSjyosieNu3wEXnjOV2cvcnLopu4vDBZdZdu/HVphpxfjRnoVnufmnuu4Ac3OPwt+i4HEv5XlwU7oRs8KWF6MGVt0sqws/oRk1Lf7VJjMv7X7cZPSltl9WFnbHWyMzchpgmZlLxOi+1t+czO3I2flPapSvfsnG7wpITZY7pjJ3SDJyXMfxVlldm94mtVlRV7G/jQBFoL2SlwkhE3nr9puOEXSPqzKvdZZV5GJt93YZNMbPpN8b30XsCkxOVgW37PwTHS1vUEK2d60eeDKa+Zm2s7lt+dxuzdWH78Jt59lWmtsueD9ExTbz/m9ce8uM+q3GTbKZaYzbRniXTVS1ri97yeq1Dzu6SxUV2x522gPV7SHr9v9ryFGugljTE7a88VabGXtMTsvT1LpAlf0tIfdOeevbNt+pJW2f2b7GSkkV/e1ogOH7QXbPWXtDniDNAzGOzAl7T2J6eEnsfzx4WJZkPnCGs1vd2d3ZNPY9h3DG1g8J4BPb3IXQNVZN03YJuDvG3uM1ZGN3BcLnS/YUo75HNVpS+cjKa0h5vUDZ+c99pUefGLn7g+jZ+WuTwSx0zbDp6Q027xh9SwFtUbOzZj+IIPnX/wnZ59dLYc5prItVJ3h2JzLPxWjn/Kx4gPKDH5HoP2UKYxXI9C+nxCJs98lM+kY3LMRngoFZtf8XYTibzYZs9i/Soes6rOy0KsBVypq5WIxF2e7bb2dryxEIlN+fDQHEe25eZw/O+NG/ZPZrddO7gZ/WkmomQWKX21nOubmyg5BR8fHD84aXSfHAOliBIZCpQkUKJAEFECoUAggYAClYgSFcHqahHHKFCRQIUCtYgSHcqoSaBGgbGIkjgUGJPAGAXORZTMQ1bnJHCOAhciShahwAUJXKDApYiSZShwSQKXKHAlomQVClyRwBUGwPIgZ6FQSeGRPXqO+MhgcAAgTJC0XEgIBlOIJKZIWjZkkCNJQZKYJGn5kDoYTGGSmCZpGZFxMJgCJTFR0nIig0xJCpXEVEnLigxyJSlYEpMlLS8yyJakcElMl7TMyFVwn6CASUwYzIagBgoYYMBADpUgUL6gt0PBUBVCYI/CeIEaqiegdAGmC/RgRQGlCzBdEA9WFFC6ANMF88GKAkoXYLpgMVhRQOkCTBcsBysKKF2A6YLVYEUBpQswXWo2WFGK4qUwXkoOVpSifCnMl4LBilIUMNXrgpYZCLZsFWiEmDBlmYFg21aUMIUJU/FgA6aAKQyYsshAEDBFAVMYMGWRgXBmCpjCgCmLDAQBUxQwhQFTFhkIAqYoYAoDpi0yEARMU8A0BkxbZGARPLVQwDQGTB/PWMtgMAVMY8C0GlwqTQHTvaOWHlwqHThtYcB0PLhUmhKmMWF6PrhUmhKmMWF6MbxUlDD30fFU/phVJtt+a07nSeK/lnkV/7pTe/vr4FUsxfr1LRIws/++dWf146ftcd0+O5pofth1SgCdlJo3UVoxtU7XVp2cXHRyctEEKsmT829xOkXl+XN6MUuvuYL2rHlK0knBgi21ae63vJnr5ICt0vw+80TmnYqeuennmWrf4XRqq05s5b4gbynpfbg3b9qbN+1klyzZ5uVXJ+XB4dgA3tRZoeblnjd1XhHocTKb9iLUk5t5oPFmzZfrF4KnxqvMVoxwJj1jepRWefojpk4t7sR4ZdTTCm5H0gNPrsbJ0ktwT9eTHaXavD305tAjT/Okzhvz2JNuB1Y8Bk9vdjwxb01k7OqCN4v99zCeqLexSLevKx6J/ULzlJwQ8KbQf5PrOfNKQ7otD3hckz/y61S9TYqn1b7K9Zz5vEnnjFcl6BrT487vXm7L0zx/+J7Sk/S+qXLoabbJ012kp+fRp1zX0HOWXvPe2Zs/ryVKcPPHkLqJxD7fZ7u8yMQ6uXl7+x97mC+L"; -------------------------------------------------------------------------------- /test/test.intermixed.arr.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - Intermixed Array', function () { 3 | // tests based on examples at http://goessner.net/articles/jsonpath/ 4 | const json = { 5 | "store": { 6 | "book": [ 7 | { 8 | "category": "reference", 9 | "author": "Nigel Rees", 10 | "title": "Sayings of the Century", 11 | "price": [8.95, 8.94, 8.93] 12 | }, 13 | { 14 | "category": "fiction", 15 | "author": "Evelyn Waugh", 16 | "title": "Sword of Honour", 17 | "price": 12.99 18 | }, 19 | { 20 | "category": "fiction", 21 | "author": "Herman Melville", 22 | "title": "Moby Dick", 23 | "isbn": "0-553-21311-3", 24 | "price": 8.99 25 | }, 26 | { 27 | "category": "fiction", 28 | "author": "J. R. R. Tolkien", 29 | "title": "The Lord of the Rings", 30 | "isbn": "0-395-19395-8", 31 | "price": 22.99 32 | } 33 | ], 34 | "bicycle": { 35 | "color": "red", 36 | "price": 19.95 37 | } 38 | } 39 | }; 40 | 41 | it('all sub properties, entire tree', () => { 42 | const books = json.store.book; 43 | let expected = [books[1].price, books[2].price, books[3].price, json.store.bicycle.price]; 44 | expected = [...books[0].price, ...expected]; 45 | const result = jsonpath({json, path: '$.store..price', flatten: true}); 46 | assert.deepEqual(result, expected); 47 | }); 48 | 49 | it('all sub properties of single element arr', () => { 50 | const book = json.store.book[0]; 51 | const input = {book}; 52 | const expected = [book.title]; 53 | const result = jsonpath({json: input, path: '$..title', flatten: true, wrap: false}); 54 | assert.deepEqual(result, expected); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/test.pointer.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - Pointers', function () { 3 | const json = { 4 | "store": { 5 | "book": [{ 6 | "category": "reference", 7 | "author": "Nigel Rees", 8 | "title": "Sayings of the Century", 9 | "price": 8.95 10 | }, 11 | { 12 | "category": "fiction", 13 | "author": "Evelyn Waugh", 14 | "title": "Sword of Honour", 15 | "price": 12.99 16 | }, 17 | { 18 | "category": "reference", 19 | "author": "Nigel Rees", 20 | "application/vnd.wordperfect": "sotc.wpd", 21 | "title": "Sayings of the Century" 22 | }, 23 | { 24 | "category": "reference", 25 | "author": "Nigel Rees", 26 | "application~vnd.wordperfect": "sotc.wpd", 27 | "title": "Sayings of the Century" 28 | }], 29 | "bicycle": { 30 | "color": "red", 31 | "price": 19.95 32 | } 33 | } 34 | }; 35 | 36 | it('array', () => { 37 | const expected = [ 38 | '/store/book/0/price', 39 | '/store/book/1/price', 40 | '/store/bicycle/price' 41 | ]; 42 | const result = jsonpath({json, path: 'store..price', resultType: 'pointer', flatten: true}); 43 | assert.deepEqual(result, expected); 44 | }); 45 | it('single', () => { 46 | const expected = ['/store']; 47 | const result = jsonpath({json, path: 'store', resultType: 'pointer', flatten: true}); 48 | assert.deepEqual(result, expected); 49 | }); 50 | 51 | it('escape / as ~1', () => { 52 | const expected = ['/store/book/2/application~1vnd.wordperfect']; 53 | const result = jsonpath({json, path: "$['store']['book'][*]['application/vnd.wordperfect']", resultType: 'pointer', flatten: true}); 54 | assert.deepEqual(result, expected); 55 | }); 56 | 57 | it('escape ~ as ~0', () => { 58 | const expected = ['/store/book/3/application~0vnd.wordperfect']; 59 | const result = jsonpath({json, path: "$['store']['book'][*]['application~vnd.wordperfect']", resultType: 'pointer', flatten: true}); 60 | assert.deepEqual(result, expected); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/test.toPath.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - toPath*', function () { 3 | it('toPathString', () => { 4 | const expected = "$['store']['bicycle']['color']"; 5 | const result = jsonpath.toPathString(['$', 'store', 'bicycle', 'color']); 6 | assert.strictEqual(result, expected); 7 | }); 8 | it('toPathString (stripped)', () => { 9 | const expected = "$['store']['bicycle']['color']"; 10 | let result = jsonpath.toPathString(['$', 'store', 'bicycle', 'color', '^']); 11 | assert.deepEqual(result, expected); 12 | result = jsonpath.toPathString(['$', 'store', 'bicycle', 'color', '@string()']); 13 | assert.deepEqual(result, expected); 14 | result = jsonpath.toPathString(['$', 'store', 'bicycle', 'color', '~']); 15 | assert.deepEqual(result, expected); 16 | }); 17 | it('toPathArray', () => { 18 | const expected = ['$', 'store', 'bicycle', 'color']; 19 | const result = jsonpath.toPathArray("$['store']['bicycle']['color']"); 20 | assert.deepEqual(result, expected); 21 | }); 22 | 23 | it('toPathArray (unnormalized)', () => { 24 | const expected = ['$', 'store', 'bicycle', 'color']; 25 | const result = jsonpath.toPathArray("$.store['bicycle'].color"); 26 | assert.deepEqual(result, expected); 27 | }); 28 | 29 | it('toPathArray (avoid cache reference issue #78)', () => { 30 | const originalPath = "$['foo']['bar']"; 31 | const json = {foo: {bar: 'baz'}}; 32 | const pathArr = jsonpath.toPathArray(originalPath); 33 | 34 | assert.strictEqual(pathArr.length, 3); 35 | 36 | // Shouldn't manipulate pathArr values 37 | jsonpath({ 38 | json, 39 | path: originalPath, 40 | wrap: false, 41 | resultType: 'value' 42 | }); 43 | 44 | assert.strictEqual(pathArr.length, 3); 45 | const path = jsonpath.toPathString(pathArr); 46 | 47 | assert.strictEqual(path, originalPath); 48 | }); 49 | 50 | it('toPathArray (cache issue)', () => { 51 | // We test here a bug where toPathArray did not return a clone of the 52 | // cached array. As a result, the evaluate call corrupted the cached 53 | // value instead of its local copy. 54 | 55 | // Make the path unique by including the test name 'cacheissue' in the 56 | // path because we do not want it to be in the cache already. 57 | const expected = ['$', 'store', 'bicycle', 'cacheissue']; 58 | const path = "$.store['bicycle'].cacheissue"; 59 | const json = {}; 60 | jsonpath({json, path, wrap: false}); 61 | const result = jsonpath.toPathArray(path); 62 | assert.deepEqual(result, expected); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/test.all.js: -------------------------------------------------------------------------------- 1 | import {checkBuiltInVMAndNodeVM} from '../test-helpers/checkVM.js'; 2 | 3 | checkBuiltInVMAndNodeVM(function (vmType, setBuiltInState) { 4 | describe(`JSONPath - All (${vmType})`, function () { 5 | before(setBuiltInState); 6 | 7 | const json = { 8 | "name": "root", 9 | "children": [ 10 | {"name": "child1", "children": [{"name": "child1_1"}, {"name": "child1_2"}]}, 11 | {"name": "child2", "children": [{"name": "child2_1"}]}, 12 | {"name": "child3", "children": [{"name": "child3_1"}, {"name": "child3_2"}]} 13 | ] 14 | }; 15 | 16 | it('simple parent selection, return both path and value', () => { 17 | const result = jsonpath({json, path: '$.children[0]^', resultType: 'all'}); 18 | assert.deepEqual( 19 | result, 20 | [{ 21 | path: "$['children']", value: json.children, 22 | parent: json, parentProperty: 'children', 23 | pointer: '/children', hasArrExpr: undefined 24 | }] 25 | ); 26 | }); 27 | 28 | it('parent selection with multiple matches, return both path and value', () => { 29 | const expectedOne = {path: "$['children']", value: json.children, parent: json, parentProperty: 'children', pointer: '/children', hasArrExpr: true}; 30 | const expected = [expectedOne, expectedOne]; 31 | const result = jsonpath({json, path: '$.children[1:3]^', resultType: 'all'}); 32 | assert.deepEqual(result, expected); 33 | }); 34 | 35 | it('select sibling via parent, return both path and value', () => { 36 | const expected = [{path: "$['children'][2]['children'][1]", value: {name: 'child3_2'}, parent: json.children[2].children, parentProperty: 1, pointer: '/children/2/children/1', hasArrExpr: true}]; 37 | const result = jsonpath({json, path: '$..[?(@.name && @.name.match(/3_1$/))]^[?(@.name.match(/_2$/))]', resultType: 'all'}); 38 | assert.deepEqual(result, expected); 39 | }); 40 | 41 | it('parent parent parent, return both path and value', () => { 42 | const expected = [{path: "$['children'][0]['children']", value: json.children[0].children, parent: json.children[0], parentProperty: 'children', pointer: '/children/0/children', hasArrExpr: true}]; 43 | const result = jsonpath({json, path: '$..[?(@.name && @.name.match(/1_1$/))].name^^', resultType: 'all'}); 44 | assert.deepEqual(result, expected); 45 | }); 46 | 47 | it('no such parent', () => { 48 | const result = jsonpath({json, path: 'name^^', resultType: 'all'}); 49 | assert.deepEqual(result, []); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/test.properties.js: -------------------------------------------------------------------------------- 1 | import {checkBuiltInVMAndNodeVM} from '../test-helpers/checkVM.js'; 2 | 3 | checkBuiltInVMAndNodeVM(function (vmType, setBuiltInState) { 4 | describe(`JSONPath - Properties (${vmType})`, function () { 5 | before(setBuiltInState); 6 | 7 | const json = { 8 | "test1": { 9 | "test2": { 10 | "test3.test4.test5": { 11 | "test7": "value" 12 | } 13 | } 14 | }, 15 | "datafield": [ 16 | {"tag": "035", "subfield": {"@code": "a", "#text": "1879"}}, 17 | {"@tag": "042", "subfield": {"@code": "a", "#text": "5555"}}, 18 | {"@tag": "045", "045": "secret"} 19 | ] 20 | }; 21 | 22 | it('Periods within properties', () => { 23 | const expected = {"test7": "value"}; 24 | const result = jsonpath({json, path: "$.test1.test2['test3.test4.test5']", wrap: false}); 25 | assert.deepEqual(result, expected); 26 | }); 27 | 28 | it('At signs within properties', () => { 29 | let result = jsonpath({json, path: "$.datafield[?(@.tag=='035')]", wrap: false}); 30 | assert.deepEqual(result, [json.datafield[0]]); 31 | result = jsonpath({json, path: "$.datafield[?(@['@tag']=='042')]", wrap: false}); 32 | assert.deepEqual(result, [json.datafield[1]]); 33 | result = jsonpath({json, path: "$.datafield[2][(@['@tag'])]", wrap: false}); 34 | assert.deepEqual(result, json.datafield[2]['045']); 35 | }); 36 | 37 | it('At signs within properties (null data)', () => { 38 | const result = jsonpath({json: { 39 | datafield: [null] 40 | }, path: "$.datafield[?(@ && @.tag=='xxx')]", wrap: false}); 41 | assert.deepEqual(result, undefined); 42 | }); 43 | 44 | it('Checking properties of child object (through `@` as parent object)', function () { 45 | const jsonObj = { 46 | test1: { 47 | a: 4, 48 | b: 8 49 | } 50 | }; 51 | const result = jsonpath({ 52 | json: jsonObj, path: "$.[?(@.a == 4)]", wrap: false}); 53 | assert.deepEqual(result, [jsonObj.test1]); 54 | }); 55 | it('Checking properties of child object (through `@` as property)', function () { 56 | const jsonObj = { 57 | test1: { 58 | a: 4, 59 | b: 8 60 | } 61 | }; 62 | const result = jsonpath({ 63 | json: jsonObj, path: "$.[?(@property == 'a' && @ == 4)]^", wrap: false}); 64 | assert.deepEqual(result, [jsonObj.test1]); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSONPath Demo 6 | 7 | 8 | 9 | 10 | JSONPath Demo (To demo on Node instead, see the library on Runkit.) 11 | 12 | 13 | 14 | JSONPath: 15 | 16 | 17 | eval: 18 | 19 | safe 20 | native 21 | false 22 | 23 | 24 | ignoreEvalErrors: 25 | 26 | false (default) 27 | true 28 | 29 | 30 | 31 | 32 | JSON sample: 33 | 34 | { 35 | "store": { 36 | "book": [ 37 | { 38 | "category": "reference", 39 | "author": "Nigel Rees", 40 | "title": "Sayings of the Century", 41 | "price": 8.95 42 | }, 43 | { 44 | "category": "fiction", 45 | "author": "Evelyn Waugh", 46 | "title": "Sword of Honour", 47 | "price": 12.99 48 | }, 49 | { 50 | "category": "fiction", 51 | "author": "Herman Melville", 52 | "title": "Moby Dick", 53 | "isbn": "0-553-21311-3", 54 | "price": 8.99 55 | }, 56 | { 57 | "category": "fiction", 58 | "author": "J. R. R. Tolkien", 59 | "title": "The Lord of the Rings", 60 | "isbn": "0-395-19395-8", 61 | "price": 22.99 62 | } 63 | ], 64 | "bicycle": { 65 | "color": "red", 66 | "price": 19.95 67 | } 68 | } 69 | } 70 | 71 | 72 | 73 | 74 | Results: 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /badges/licenses-badge-dev.svg: -------------------------------------------------------------------------------- 1 | License typesLicense types(all devDeps)(all devDeps)PublicPublicdomaindomain1. (MIT OR CC0-1.0)1. (MIT OR CC0-1.0)2. CC0-1.02. CC0-1.0PermissivePermissive1. (BSD-2-Clause OR (MIT OR Apache-2.0))1. (BSD-2-Clause OR (MIT OR Apache-2.0))2. (MIT OR CC0-1.0)2. (MIT OR CC0-1.0)3. (WTFPL OR MIT)3. (WTFPL OR MIT)4. Apache-2.04. Apache-2.05. BSD-2-Clause5. BSD-2-Clause6. BSD-3-Clause6. BSD-3-Clause7. BlueOak-1.0.07. BlueOak-1.0.08. CC-BY-3.08. CC-BY-3.09. CC-BY-4.09. CC-BY-4.010. ISC10. ISC11. MIT11. MIT12. Python-2.012. Python-2.0MissingMissing1. union (0.5.0)1. union (0.5.0) 2 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import ashNazg from 'eslint-config-ash-nazg'; 2 | 3 | export default [ 4 | { 5 | ignores: [ 6 | '.github', 7 | '.idea', 8 | 'dist', 9 | 'docs/ts', 10 | 'coverage', 11 | 'ignore' 12 | ] 13 | }, 14 | ...ashNazg(['sauron', 'node', 'browser']), 15 | { 16 | settings: { 17 | polyfills: [ 18 | 'document.querySelector' 19 | ] 20 | } 21 | }, 22 | { 23 | files: ['src/jsonpath-node.js', 'test-helpers/node-env.js'], 24 | rules: { 25 | 'n/no-unsupported-features/es-syntax': ['error', { 26 | ignores: [ 27 | 'regexpNamedCaptureGroups', 'modules', 'dynamicImport' 28 | ] 29 | }] 30 | } 31 | }, 32 | { 33 | files: ['*.md/*.js', '*.md/*.html'], 34 | rules: { 35 | 'import/unambiguous': 0, 36 | 'import/no-commonjs': 0, 37 | 'import/no-unresolved': ['error', { 38 | ignore: ['jsonpath-plus'] 39 | }], 40 | 'sonarjs/no-internal-api-use': 0, 41 | 'no-multiple-empty-lines': ['error', { 42 | max: 1, maxEOF: 2, maxBOF: 2 43 | }], 44 | 'no-undef': 0, 45 | 'no-unused-vars': ['error', { 46 | varsIgnorePattern: 'json|result' 47 | }], 48 | 'import/no-extraneous-dependencies': 0, 49 | 'n/no-extraneous-import': ['error', { 50 | allowModules: ['jsonpath-plus'] 51 | }], 52 | 'n/no-missing-require': ['error', { 53 | allowModules: ['jsonpath-plus'] 54 | }], 55 | // Unfortunately, with the new processor approach, the filename 56 | // is now README.md so our paths must be `../`. However, even 57 | // with that, eslint-plugin-node is not friendly to such 58 | // imports, so we disable 59 | 'n/no-missing-import': 'off', 60 | 'n/no-unpublished-import': 'off' 61 | } 62 | }, 63 | { 64 | files: ['test/**'], 65 | languageOptions: { 66 | globals: { 67 | assert: 'readonly', 68 | expect: 'readonly', 69 | jsonpath: 'readonly' 70 | } 71 | }, 72 | rules: { 73 | '@stylistic/quotes': 0, 74 | '@stylistic/quote-props': 0, 75 | 'import/unambiguous': 0, 76 | // Todo: Reenable 77 | '@stylistic/max-len': 0 78 | } 79 | }, 80 | { 81 | rules: { 82 | '@stylistic/indent': ['error', 4, {outerIIFEBody: 0}], 83 | 'promise/prefer-await-to-callbacks': 0, 84 | 85 | // Disable for now 86 | 'new-cap': 0, 87 | 'jsdoc/reject-any-type': 0, 88 | '@stylistic/dot-location': 0, 89 | // Reenable as have time and confirming no longer needing: 90 | // https://github.com/babel/babel/issues/8951#issuecomment-508045524 91 | 'prefer-named-capture-group': 0, 92 | 'unicorn/prefer-spread': 0 93 | } 94 | } 95 | ]; 96 | -------------------------------------------------------------------------------- /src/jsonpath-browser.js: -------------------------------------------------------------------------------- 1 | import {JSONPath} from './jsonpath.js'; 2 | 3 | /** 4 | * @typedef {any} ContextItem 5 | */ 6 | 7 | /** 8 | * @typedef {any} EvaluatedResult 9 | */ 10 | 11 | /** 12 | * @callback ConditionCallback 13 | * @param {ContextItem} item 14 | * @returns {boolean} 15 | */ 16 | 17 | /** 18 | * Copy items out of one array into another. 19 | * @param {GenericArray} source Array with items to copy 20 | * @param {GenericArray} target Array to which to copy 21 | * @param {ConditionCallback} conditionCb Callback passed the current item; 22 | * will move item if evaluates to `true` 23 | * @returns {void} 24 | */ 25 | const moveToAnotherArray = function (source, target, conditionCb) { 26 | const il = source.length; 27 | for (let i = 0; i < il; i++) { 28 | const item = source[i]; 29 | if (conditionCb(item)) { 30 | // eslint-disable-next-line @stylistic/max-len -- Long 31 | // eslint-disable-next-line sonarjs/updated-loop-counter -- Convenient 32 | target.push(source.splice(i--, 1)[0]); 33 | } 34 | } 35 | }; 36 | 37 | /** 38 | * In-browser replacement for NodeJS' VM.Script. 39 | */ 40 | class Script { 41 | /** 42 | * @param {string} expr Expression to evaluate 43 | */ 44 | constructor (expr) { 45 | this.code = expr; 46 | } 47 | 48 | /** 49 | * @param {object} context Object whose items will be added 50 | * to evaluation 51 | * @returns {EvaluatedResult} Result of evaluated code 52 | */ 53 | runInNewContext (context) { 54 | let expr = this.code; 55 | const keys = Object.keys(context); 56 | const funcs = []; 57 | moveToAnotherArray(keys, funcs, (key) => { 58 | return typeof context[key] === 'function'; 59 | }); 60 | const values = keys.map((vr) => { 61 | return context[vr]; 62 | }); 63 | 64 | const funcString = funcs.reduce((s, func) => { 65 | let fString = context[func].toString(); 66 | if (!(/function/u).test(fString)) { 67 | fString = 'function ' + fString; 68 | } 69 | return 'var ' + func + '=' + fString + ';' + s; 70 | }, ''); 71 | 72 | expr = funcString + expr; 73 | 74 | // Mitigate http://perfectionkills.com/global-eval-what-are-the-options/#new_function 75 | if (!(/(['"])use strict\1/u).test(expr) && !keys.includes('arguments')) { 76 | expr = 'var arguments = undefined;' + expr; 77 | } 78 | 79 | // Remove last semi so `return` will be inserted before 80 | // the previous one instead, allowing for the return 81 | // of a bare ending expression 82 | expr = expr.replace(/;\s*$/u, ''); 83 | 84 | // Insert `return` 85 | const lastStatementEnd = expr.lastIndexOf(';'); 86 | const code = 87 | lastStatementEnd !== -1 88 | ? expr.slice(0, lastStatementEnd + 1) + 89 | ' return ' + 90 | expr.slice(lastStatementEnd + 1) 91 | : ' return ' + expr; 92 | 93 | // eslint-disable-next-line no-new-func -- User's choice 94 | return new Function(...keys, code)(...values); 95 | } 96 | } 97 | 98 | JSONPath.prototype.vm = { 99 | Script 100 | }; 101 | 102 | export {JSONPath}; 103 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import {readFile} from 'fs/promises'; 2 | import {babel} from '@rollup/plugin-babel'; 3 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 4 | import terser from '@rollup/plugin-terser'; 5 | 6 | const pkg = JSON.parse(await readFile('./package.json')); 7 | 8 | /** 9 | * @external RollupConfig 10 | * @type {object} 11 | * @see {@link https://rollupjs.org/guide/en#big-list-of-options} 12 | */ 13 | 14 | /** 15 | * @param {object} config 16 | * @param {string} config.input 17 | * @param {boolean} config.minifying 18 | * @param {string[]} [config."external"] 19 | * @param {string} [config.environment] 20 | * @param {string} [config.format] 21 | * @returns {RollupConfig} 22 | */ 23 | function getRollupObject ({ 24 | input, minifying, environment, 25 | // eslint-disable-next-line no-shadow -- Ok 26 | external, 27 | format = 'umd' 28 | }) { 29 | const nonMinified = { 30 | input, 31 | external, 32 | output: { 33 | format, 34 | sourcemap: minifying, 35 | file: `dist/index${environment ? `-${environment}` : ''}-${ 36 | format 37 | }${minifying ? '.min' : ''}.${ 38 | format === 'esm' ? '' : 'c' 39 | }js`, 40 | name: 'JSONPath' 41 | }, 42 | plugins: [ 43 | babel({ 44 | babelrc: false, 45 | presets: [ 46 | environment === 'node' || environment === 'cli' 47 | ? ['@babel/preset-env', { 48 | targets: [ 49 | `node ${pkg.engines.node}` 50 | ] 51 | }] 52 | // Can come up with some browser targets 53 | : ['@babel/preset-env'] 54 | ], 55 | babelHelpers: 'bundled' 56 | }), 57 | nodeResolve() 58 | ] 59 | }; 60 | if (minifying) { 61 | nonMinified.plugins.push(terser()); 62 | } 63 | return nonMinified; 64 | } 65 | 66 | /** 67 | * @param {PlainObject} config 68 | * @param {boolean} config.minifying 69 | * @param {"node"|"environment"} [config.environment] 70 | * @returns {RollupConfig[]} 71 | */ 72 | function getRollupObjectByEnv ({minifying, environment}) { 73 | const input = `src/jsonpath-${environment}.js`; 74 | if (environment === 'node') { 75 | // eslint-disable-next-line no-shadow -- Ok 76 | const external = ['vm']; 77 | return [ 78 | getRollupObject({ 79 | input, minifying, environment, external, format: 'cjs' 80 | }), 81 | getRollupObject({ 82 | input, minifying, environment, external, format: 'esm' 83 | }) 84 | ]; 85 | } 86 | return [ 87 | getRollupObject({input, minifying, environment, format: 'umd'}), 88 | getRollupObject({input, minifying, environment, format: 'esm'}) 89 | ]; 90 | } 91 | 92 | export default [ 93 | ...getRollupObjectByEnv({minifying: false, environment: 'node'}), 94 | // ...getRollupObjectByEnv({minifying: true, environment: 'node'}), 95 | // getRollupObject({ 96 | // input: 'bin/jsonpath-cli.js', format: 'esm', 97 | // minifying: false, environment: 'cli', 98 | // external: ['fs/promises', 'vm'] 99 | // }), 100 | ...getRollupObjectByEnv({minifying: false, environment: 'browser'}), 101 | ...getRollupObjectByEnv({minifying: true, environment: 'browser'}) 102 | ]; 103 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | /* globals JSONPath, LZString -- Test UMD */ 3 | /* eslint-disable import/unambiguous -- Demo */ 4 | 5 | // Todo: Extract testing example paths/contents and use for a 6 | // pulldown that can populate examples 7 | 8 | // Todo: Make configurable with other JSONPath options 9 | 10 | // Todo: Allow source to be treated as an (evaled) JSON object 11 | 12 | // Todo: Could add JSON/JS syntax highlighting in sample and result, 13 | // ideally with a jsonpath-plus parser highlighter as well 14 | 15 | const $ = (s) => document.querySelector(s); 16 | 17 | const jsonpathEl = $('#jsonpath'); 18 | const jsonSample = $('#jsonSample'); 19 | 20 | const updateUrl = () => { 21 | const path = jsonpathEl.value; 22 | const jsonText = LZString.compressToEncodedURIComponent(jsonSample.value); 23 | const url = new URL(location.href); 24 | url.searchParams.set('path', path); 25 | url.searchParams.set('json', jsonText); 26 | url.searchParams.set('eval', $('#eval').value); 27 | url.searchParams.set('ignoreEvalErrors', $('#ignoreEvalErrors').value); 28 | history.replaceState(null, '', url.toString()); 29 | }; 30 | 31 | const loadUrl = () => { 32 | const url = new URL(location.href); 33 | if (url.searchParams.has('path')) { 34 | jsonpathEl.value = url.searchParams.get('path'); 35 | } 36 | if (url.searchParams.has('json')) { 37 | jsonSample.value = LZString.decompressFromEncodedURIComponent( 38 | url.searchParams.get('json') 39 | ); 40 | } 41 | if (url.searchParams.has('eval')) { 42 | $('#eval').value = url.searchParams.get('eval'); 43 | } 44 | if (url.searchParams.has('ignoreEvalErrors')) { 45 | $('#ignoreEvalErrors').value = url.searchParams.get('ignoreEvalErrors'); 46 | } 47 | }; 48 | 49 | const updateResults = () => { 50 | const reportValidity = () => { 51 | // Doesn't work without a timeout 52 | setTimeout(() => { 53 | jsonSample.reportValidity(); 54 | jsonpathEl.reportValidity(); 55 | }); 56 | }; 57 | let json; 58 | jsonSample.setCustomValidity(''); 59 | jsonpathEl.setCustomValidity(''); 60 | reportValidity(); 61 | try { 62 | json = JSON.parse(jsonSample.value); 63 | } catch (err) { 64 | jsonSample.setCustomValidity('Error parsing JSON: ' + err.toString()); 65 | reportValidity(); 66 | return; 67 | } 68 | try { 69 | const result = new JSONPath.JSONPath({ 70 | path: jsonpathEl.value, 71 | json, 72 | eval: $('#eval').value === 'false' ? false : $('#eval').value, 73 | ignoreEvalErrors: $('#ignoreEvalErrors').value === 'true' 74 | }); 75 | $('#results').value = JSON.stringify(result, null, 2); 76 | } catch (err) { 77 | jsonpathEl.setCustomValidity( 78 | 'Error executing JSONPath: ' + err.toString() 79 | ); 80 | reportValidity(); 81 | $('#results').value = ''; 82 | } 83 | }; 84 | 85 | $('#jsonpath').addEventListener('input', () => { 86 | updateUrl(); 87 | updateResults(); 88 | }); 89 | 90 | $('#jsonSample').addEventListener('input', () => { 91 | updateUrl(); 92 | updateResults(); 93 | }); 94 | 95 | $('#eval').addEventListener('change', () => { 96 | updateUrl(); 97 | updateResults(); 98 | }); 99 | 100 | $('#ignoreEvalErrors').addEventListener('change', () => { 101 | updateUrl(); 102 | updateResults(); 103 | }); 104 | 105 | window.addEventListener('load', () => { 106 | loadUrl(); 107 | updateResults(); 108 | }); 109 | -------------------------------------------------------------------------------- /test/test.api.js: -------------------------------------------------------------------------------- 1 | describe('JSONPath - API', function () { 2 | // tests based on examples at http://goessner.net/articles/jsonpath/ 3 | const json = { 4 | "store": { 5 | "book": [{ 6 | "category": "reference", 7 | "author": "Nigel Rees", 8 | "title": "Sayings of the Century", 9 | "price": 8.95 10 | }, 11 | { 12 | "category": "fiction", 13 | "author": "Evelyn Waugh", 14 | "title": "Sword of Honour", 15 | "price": 12.99 16 | }, 17 | { 18 | "category": "fiction", 19 | "author": "Herman Melville", 20 | "title": "Moby Dick", 21 | "isbn": "0-553-21311-3", 22 | "price": 8.99 23 | }, 24 | { 25 | "category": "fiction", 26 | "author": "J. R. R. Tolkien", 27 | "title": "The Lord of the Rings", 28 | "isbn": "0-395-19395-8", 29 | "price": 22.99 30 | }], 31 | "bicycle": { 32 | "color": "red", 33 | "price": 19.95 34 | } 35 | } 36 | }; 37 | 38 | it('should test non-object argument of constructor', () => { 39 | const books = json.store.book; 40 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 41 | let result = jsonpath('$.store.book[*].author', json); 42 | assert.deepEqual(result, expected); 43 | result = jsonpath({json, path: 'store.book[*].author'}); 44 | assert.deepEqual(result, expected); 45 | }); 46 | 47 | it('should test array path of constructor', () => { 48 | const books = json.store.book; 49 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 50 | let result = jsonpath({path: ['$', 'store', 'book', '*', 'author'], json}); 51 | assert.deepEqual(result, expected); 52 | result = jsonpath({json, path: 'store.book[*].author'}); 53 | assert.deepEqual(result, expected); 54 | }); 55 | 56 | it('should test defaults on manual `evaluate` with `autostart: false`', () => { 57 | const books = json.store.book; 58 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 59 | let jp = jsonpath({ 60 | path: '$.store.book[*].author', 61 | json, 62 | autostart: false 63 | }); 64 | let result = jp.evaluate(); 65 | assert.deepEqual(result, expected); 66 | jp = jsonpath({ 67 | json, 68 | path: 'store.book[*].author', 69 | autostart: false 70 | }); 71 | result = jp.evaluate(); 72 | assert.deepEqual(result, expected); 73 | }); 74 | 75 | it('should test defaults with `evaluate` object and `autostart: false`', () => { 76 | const books = json.store.book; 77 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 78 | const jp = jsonpath({ 79 | autostart: false 80 | }); 81 | const result = jp.evaluate({ 82 | json, 83 | path: '$.store.book[*].author', 84 | sandbox: {category: 'reference'}, 85 | eval: false, 86 | flatten: true, 87 | wrap: false, 88 | resultType: 'value', 89 | callback () { /* */ }, 90 | parent: null, 91 | parentProperty: null, 92 | otherTypeCallback () { /* */ } 93 | }); 94 | assert.deepEqual(result, expected); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /test/test.path_expressions.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - Path expressions', function () { 3 | // tests based on examples at http://goessner.net/articles/JsonPath/ 4 | 5 | const json = {"store": { 6 | "book": [ 7 | { 8 | "category": "reference", 9 | "author": "Nigel Rees", 10 | "application/vnd.wordperfect": "sotc.wpd", 11 | "title": "Sayings of the Century", 12 | "price": 8.95 13 | }, 14 | { 15 | "category": "fiction", 16 | "author": "Evelyn Waugh", 17 | "title": "Sword of Honour", 18 | "price": 12.99 19 | }, 20 | { 21 | "category": "fiction", 22 | "author": "Herman Melville", 23 | "title": "Moby Dick", 24 | "isbn": "0-553-21311-3", 25 | "price": 8.99 26 | }, 27 | { 28 | "category": "fiction", 29 | "author": "J. R. R. Tolkien", 30 | "title": "The Lord of the Rings", 31 | "isbn": "0-395-19395-8", 32 | "price": 22.99 33 | } 34 | ], 35 | "bicycle": { 36 | "color": "red", 37 | "price": 19.95 38 | } 39 | }}; 40 | 41 | it('dot notation', () => { 42 | const books = json.store.book; 43 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 44 | const result = jsonpath({json, path: '$.store.book[*].author'}); 45 | assert.deepEqual(result, expected); 46 | }); 47 | 48 | it('bracket notation', () => { 49 | const books = json.store.book; 50 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 51 | const result = jsonpath({json, path: "$['store']['book'][*]['author']"}); 52 | assert.deepEqual(result, expected); 53 | }); 54 | 55 | it('bracket notation (double quoted)', () => { 56 | const books = json.store.book; 57 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 58 | const result = jsonpath({json, path: '$["store"]["book"][*]["author"]'}); 59 | assert.deepEqual(result, expected); 60 | }); 61 | 62 | it('bracket notation without quotes', () => { 63 | const books = json.store.book; 64 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 65 | const result = jsonpath({json, path: "$[store][book][*][author]"}); 66 | assert.deepEqual(result, expected); 67 | }); 68 | 69 | it('mixed notation', () => { 70 | const books = json.store.book; 71 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 72 | const result = jsonpath({json, path: "$.store.book[*]['author']"}); 73 | assert.deepEqual(result, expected); 74 | }); 75 | 76 | it('bracket notation containing dots', () => { 77 | const books = json.store.book; 78 | const expected = [books[0]["application/vnd.wordperfect"]]; 79 | const result = jsonpath({json, path: "$['store']['book'][*]['application/vnd.wordperfect']"}); 80 | assert.deepEqual(result, expected); 81 | }); 82 | 83 | it('mixed notation containing dots', () => { 84 | const books = json.store.book; 85 | const expected = [books[0]["application/vnd.wordperfect"]]; 86 | const result = jsonpath({json, path: "$.store.book[*]['application/vnd.wordperfect']"}); 87 | assert.deepEqual(result, expected); 88 | }); 89 | 90 | it('empty string key', () => { 91 | const jsonSimple = { 92 | '': null 93 | }; 94 | const expected = null; 95 | const result = jsonpath({json: jsonSimple, path: '', wrap: false}); 96 | assert.deepEqual(result, expected); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/test.errors.js: -------------------------------------------------------------------------------- 1 | import {checkBuiltInVMAndNodeVM} from '../test-helpers/checkVM.js'; 2 | 3 | checkBuiltInVMAndNodeVM(function (vmType, setBuiltInState) { 4 | describe(`JSONPath - Error (${vmType})`, function () { 5 | before(setBuiltInState); 6 | 7 | it('should throw with missing `path`', function () { 8 | assert.throws(() => { 9 | jsonpath({json: []}); 10 | }, TypeError, 'You must supply a "path" property when providing an object ' + 11 | 'argument to JSONPath.evaluate().'); 12 | }); 13 | it('should throw with missing `json`', function () { 14 | assert.throws(() => { 15 | jsonpath({path: '$'}); 16 | }, TypeError, 'You must supply a "json" property when providing an object ' + 17 | 'argument to JSONPath.evaluate().'); 18 | }); 19 | 20 | it('should throw with a bad filter', () => { 21 | expect(() => { 22 | jsonpath({json: {book: []}, path: '$..[?(@.category === category)]'}); 23 | }).to.throw(Error, 'jsonPath: category is not defined: @.category === category'); 24 | }); 25 | 26 | it('should throw with a bad result type', () => { 27 | expect(() => { 28 | jsonpath({ 29 | json: {children: [5]}, 30 | path: '$..children', 31 | resultType: 'badType' 32 | }); 33 | }).to.throw(TypeError, 'Unknown result type'); 34 | }); 35 | 36 | it('should throw with `eval: false` and [?()] filtering expression', () => { 37 | expect(() => { 38 | const json = { 39 | datafield: [ 40 | {"tag": "035", "subfield": {"@code": "a", "#text": "1879"}}, 41 | {"@tag": "042", "subfield": {"@code": "a", "#text": "5555"}}, 42 | {"@tag": "045", "045": "secret"} 43 | ] 44 | }; 45 | jsonpath({ 46 | json, 47 | path: "$.datafield[?(@.tag=='035')]", 48 | eval: false 49 | }); 50 | }).to.throw(Error, 'Eval [?(expr)] prevented in JSONPath expression.'); 51 | }); 52 | 53 | it('should throw with `eval: false` and [?()] filtering expression (@.length)', () => { 54 | expect(() => { 55 | const json = { 56 | datafield: [ 57 | {"tag": "035", "subfield": {"@code": "a", "#text": "1879"}}, 58 | {"@tag": "042", "subfield": {"@code": "a", "#text": "5555"}}, 59 | {"@tag": "045", "045": "secret"} 60 | ] 61 | }; 62 | jsonpath({ 63 | json, 64 | path: '$..datafield[(@.length-1)]', 65 | eval: false 66 | }); 67 | }).to.throw(Error, 'Eval [(expr)] prevented in JSONPath expression.'); 68 | }); 69 | 70 | it('Syntax error in safe mode script', () => { 71 | expect(() => { 72 | const json = {tag: 10}; 73 | jsonpath({ 74 | json, 75 | path: '$..[?(this)]', 76 | wrap: false, 77 | eval: 'safe' 78 | }); 79 | }).to.throw(Error, 'jsonPath: Unexpected expression: this'); 80 | }); 81 | 82 | it('Invalid assignment in safe mode script', () => { 83 | expect(() => { 84 | const json = {tag: 10}; 85 | jsonpath({ 86 | json, 87 | path: '$..[?(2 = 8)]', 88 | wrap: false, 89 | eval: 'safe' 90 | }); 91 | }).to.throw(Error, 'jsonPath: Invalid left-hand side in assignment: 2 = 8'); 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /docs/ts/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #0000FF; 9 | --dark-hl-3: #569CD6; 10 | --light-hl-4: #0070C1; 11 | --dark-hl-4: #4FC1FF; 12 | --light-hl-5: #001080; 13 | --dark-hl-5: #9CDCFE; 14 | --light-hl-6: #800000; 15 | --dark-hl-6: #808080; 16 | --light-hl-7: #800000; 17 | --dark-hl-7: #569CD6; 18 | --light-hl-8: #000000FF; 19 | --dark-hl-8: #D4D4D4; 20 | --light-hl-9: #E50000; 21 | --dark-hl-9: #9CDCFE; 22 | --light-hl-10: #0000FF; 23 | --dark-hl-10: #CE9178; 24 | --light-hl-11: #AF00DB; 25 | --dark-hl-11: #C586C0; 26 | --light-hl-12: #0451A5; 27 | --dark-hl-12: #9CDCFE; 28 | --light-hl-13: #098658; 29 | --dark-hl-13: #B5CEA8; 30 | --light-code-background: #FFFFFF; 31 | --dark-code-background: #1E1E1E; 32 | } 33 | 34 | @media (prefers-color-scheme: light) { :root { 35 | --hl-0: var(--light-hl-0); 36 | --hl-1: var(--light-hl-1); 37 | --hl-2: var(--light-hl-2); 38 | --hl-3: var(--light-hl-3); 39 | --hl-4: var(--light-hl-4); 40 | --hl-5: var(--light-hl-5); 41 | --hl-6: var(--light-hl-6); 42 | --hl-7: var(--light-hl-7); 43 | --hl-8: var(--light-hl-8); 44 | --hl-9: var(--light-hl-9); 45 | --hl-10: var(--light-hl-10); 46 | --hl-11: var(--light-hl-11); 47 | --hl-12: var(--light-hl-12); 48 | --hl-13: var(--light-hl-13); 49 | --code-background: var(--light-code-background); 50 | } } 51 | 52 | @media (prefers-color-scheme: dark) { :root { 53 | --hl-0: var(--dark-hl-0); 54 | --hl-1: var(--dark-hl-1); 55 | --hl-2: var(--dark-hl-2); 56 | --hl-3: var(--dark-hl-3); 57 | --hl-4: var(--dark-hl-4); 58 | --hl-5: var(--dark-hl-5); 59 | --hl-6: var(--dark-hl-6); 60 | --hl-7: var(--dark-hl-7); 61 | --hl-8: var(--dark-hl-8); 62 | --hl-9: var(--dark-hl-9); 63 | --hl-10: var(--dark-hl-10); 64 | --hl-11: var(--dark-hl-11); 65 | --hl-12: var(--dark-hl-12); 66 | --hl-13: var(--dark-hl-13); 67 | --code-background: var(--dark-code-background); 68 | } } 69 | 70 | :root[data-theme='light'] { 71 | --hl-0: var(--light-hl-0); 72 | --hl-1: var(--light-hl-1); 73 | --hl-2: var(--light-hl-2); 74 | --hl-3: var(--light-hl-3); 75 | --hl-4: var(--light-hl-4); 76 | --hl-5: var(--light-hl-5); 77 | --hl-6: var(--light-hl-6); 78 | --hl-7: var(--light-hl-7); 79 | --hl-8: var(--light-hl-8); 80 | --hl-9: var(--light-hl-9); 81 | --hl-10: var(--light-hl-10); 82 | --hl-11: var(--light-hl-11); 83 | --hl-12: var(--light-hl-12); 84 | --hl-13: var(--light-hl-13); 85 | --code-background: var(--light-code-background); 86 | } 87 | 88 | :root[data-theme='dark'] { 89 | --hl-0: var(--dark-hl-0); 90 | --hl-1: var(--dark-hl-1); 91 | --hl-2: var(--dark-hl-2); 92 | --hl-3: var(--dark-hl-3); 93 | --hl-4: var(--dark-hl-4); 94 | --hl-5: var(--dark-hl-5); 95 | --hl-6: var(--dark-hl-6); 96 | --hl-7: var(--dark-hl-7); 97 | --hl-8: var(--dark-hl-8); 98 | --hl-9: var(--dark-hl-9); 99 | --hl-10: var(--dark-hl-10); 100 | --hl-11: var(--dark-hl-11); 101 | --hl-12: var(--dark-hl-12); 102 | --hl-13: var(--dark-hl-13); 103 | --code-background: var(--dark-code-background); 104 | } 105 | 106 | .hl-0 { color: var(--hl-0); } 107 | .hl-1 { color: var(--hl-1); } 108 | .hl-2 { color: var(--hl-2); } 109 | .hl-3 { color: var(--hl-3); } 110 | .hl-4 { color: var(--hl-4); } 111 | .hl-5 { color: var(--hl-5); } 112 | .hl-6 { color: var(--hl-6); } 113 | .hl-7 { color: var(--hl-7); } 114 | .hl-8 { color: var(--hl-8); } 115 | .hl-9 { color: var(--hl-9); } 116 | .hl-10 { color: var(--hl-10); } 117 | .hl-11 { color: var(--hl-11); } 118 | .hl-12 { color: var(--hl-12); } 119 | .hl-13 { color: var(--hl-13); } 120 | pre, code { background: var(--code-background); } 121 | -------------------------------------------------------------------------------- /docs/ts/hierarchy.html: -------------------------------------------------------------------------------- 1 | jsonpath-plusPreparing search index...The search index is not availablejsonpath-plusjsonpath-plusHierarchy SummaryJSONPathOptionsJSONPathOptionsAutoStartSettingsMember VisibilityProtectedInheritedThemeOSLightDarkjsonpath-plusLoading... 2 | -------------------------------------------------------------------------------- /test/test.parent-selector.js: -------------------------------------------------------------------------------- 1 | import {checkBuiltInVMAndNodeVM} from '../test-helpers/checkVM.js'; 2 | 3 | checkBuiltInVMAndNodeVM(function (vmType, setBuiltInState) { 4 | describe(`JSONPath - Parent selector (${vmType})`, function () { 5 | before(setBuiltInState); 6 | const json = { 7 | "name": "root", 8 | "children": [ 9 | {"name": "child1", "children": [{"name": "child1_1"}, {"name": "child1_2"}]}, 10 | {"name": "child2", "children": [{"name": "child2_1"}]}, 11 | {"name": "child3", "children": [{"name": "child3_1"}, {"name": "child3_2"}]} 12 | ] 13 | }; 14 | 15 | it('simple parent selection', () => { 16 | const result = jsonpath({json, path: '$.children[0]^', flatten: true}); 17 | assert.deepEqual(result, json.children); 18 | }); 19 | 20 | it('parent selection with multiple matches', () => { 21 | const expected = [json.children, json.children]; 22 | const result = jsonpath({json, path: '$.children[1:3]^'}); 23 | assert.deepEqual(result, expected); 24 | }); 25 | 26 | it('select sibling via parent', () => { 27 | const expected = [{"name": "child3_2"}]; 28 | const result = jsonpath({json, path: '$..[?(@.name && @.name.match(/3_1$/))]^[?(@.name.match(/_2$/))]'}); 29 | assert.deepEqual(result, expected); 30 | }); 31 | 32 | it('parent parent parent', () => { 33 | const expected = json.children[0].children; 34 | const result = jsonpath({json, path: '$..[?(@.name && @.name.match(/1_1$/))].name^^', flatten: true}); 35 | assert.deepEqual(result, expected); 36 | }); 37 | 38 | // Todo: Handle "this._trace(...).filter is not a function" error` 39 | /* 40 | it.only('parent root', () => { 41 | const jsonSimple = { 42 | children: null 43 | }; 44 | const expected = jsonSimple.children; 45 | const result = jsonpath({json: jsonSimple, path: '$^', flatten: true}); 46 | console.log('result', result); 47 | assert.deepEqual(result, expected); 48 | }); 49 | */ 50 | 51 | it('empty string key (parent of)', () => { 52 | const jsonSimple = { 53 | '': null 54 | }; 55 | const expected = jsonSimple; 56 | const result = jsonpath({json: jsonSimple, path: '^', wrap: false}); 57 | assert.deepEqual(result, expected); 58 | }); 59 | 60 | it('no such parent', () => { 61 | const result = jsonpath({json, path: 'name^^'}); 62 | assert.deepEqual(result, []); 63 | }); 64 | 65 | it('select sibling via parent (with non-match present)', () => { 66 | const jsonMultipleChildren = { 67 | "name": "root", 68 | "children": [ 69 | {"name": "child1", "children": [{"name": "child1_1"}, {"name": "child1_2"}]}, 70 | {"name": "child2", "children": [{"name": "child2_1"}]}, 71 | {"name": "child3", "children": [{"name": "child3_1"}, {"name": "child3_2"}]}, 72 | {"name": "child4", "children": [{"name": "child4_1"}, {"name": "child3_1"}]} 73 | ] 74 | }; 75 | const expected = [{"name": "child3_2"}]; 76 | const result = jsonpath({ 77 | json: jsonMultipleChildren, 78 | path: '$..[?(@.name && @.name.match(/3_1$/))]^[?(@.name.match(/_2$/))]' 79 | }); 80 | assert.deepEqual(result, expected); 81 | }); 82 | it('select sibling via parent (with multiple results)', () => { 83 | const jsonMultipleChildren = { 84 | "name": "root", 85 | "children": [ 86 | {"name": "child1", "children": [{"name": "child1_1"}, {"name": "child1_2"}]}, 87 | {"name": "child2", "children": [{"name": "child2_1"}]}, 88 | {"name": "child3", "children": [{"name": "child3_1"}, {"name": "child3_2"}, {"name": "child3_2", second: true}]} 89 | ] 90 | }; 91 | const expected = [{"name": "child3_2"}, {"name": "child3_2", second: true}]; 92 | const result = jsonpath({ 93 | json: jsonMultipleChildren, 94 | path: '$..[?(@.name && @.name.match(/3_1$/))]^[?(@.name.match(/_2$/))]' 95 | }); 96 | assert.deepEqual(result, expected); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /docs/ts/types/JSONPathType.html: -------------------------------------------------------------------------------- 1 | JSONPathType | jsonpath-plusPreparing search index...The search index is not availablejsonpath-plusjsonpath-plusJSONPathTypeType Alias JSONPathTypeJSONPathType: JSONPathCallable & JSONPathClassSettingsMember VisibilityProtectedInheritedThemeOSLightDarkjsonpath-plusLoading... 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Stefan Goessner", 3 | "name": "jsonpath-plus", 4 | "version": "10.3.0", 5 | "type": "module", 6 | "bin": { 7 | "jsonpath": "./bin/jsonpath-cli.js", 8 | "jsonpath-plus": "./bin/jsonpath-cli.js" 9 | }, 10 | "main": "dist/index-node-cjs.cjs", 11 | "exports": { 12 | "./package.json": "./package.json", 13 | ".": { 14 | "types": "./src/jsonpath.d.ts", 15 | "browser": "./dist/index-browser-esm.js", 16 | "umd": "./dist/index-browser-umd.cjs", 17 | "import": "./dist/index-node-esm.js", 18 | "require": "./dist/index-node-cjs.cjs", 19 | "default": "./dist/index-browser-esm.js" 20 | } 21 | }, 22 | "module": "dist/index-node-esm.js", 23 | "browser": "dist/index-browser-esm.js", 24 | "types": "./src/jsonpath.d.ts", 25 | "description": "A JS implementation of JSONPath with some additional operators", 26 | "contributors": [ 27 | { 28 | "name": "Prof. Gössner", 29 | "email": "stefan.goessner@fh-dortmund.de" 30 | }, 31 | { 32 | "name": "Subbu Allamaraju", 33 | "email": "subbu@subbu.org" 34 | }, 35 | { 36 | "name": "Mike Brevoort", 37 | "email": "mike@brevoort.com" 38 | }, 39 | { 40 | "name": "Robert Krahn", 41 | "email": "robert.krahn@gmail.com" 42 | }, 43 | { 44 | "name": "Brett Zamir", 45 | "email": "brettz9@yahoo.com" 46 | }, 47 | { 48 | "name": "Richard Schneider", 49 | "email": "makaretu@gmail.com" 50 | } 51 | ], 52 | "license": "MIT", 53 | "repository": { 54 | "type": "git", 55 | "url": "git://github.com/s3u/JSONPath.git" 56 | }, 57 | "bugs": "https://github.com/s3u/JSONPath/issues/", 58 | "homepage": "https://github.com/s3u/JSONPath", 59 | "engines": { 60 | "node": ">=18.0.0" 61 | }, 62 | "react-native": { 63 | "vm": false 64 | }, 65 | "dependencies": { 66 | "@jsep-plugin/assignment": "^1.3.0", 67 | "@jsep-plugin/regex": "^1.0.4", 68 | "jsep": "^1.4.0" 69 | }, 70 | "devDependencies": { 71 | "@babel/core": "^7.28.5", 72 | "@babel/preset-env": "^7.28.5", 73 | "@rollup/plugin-babel": "^6.1.0", 74 | "@rollup/plugin-node-resolve": "^16.0.3", 75 | "@rollup/plugin-terser": "^0.4.4", 76 | "c8": "^10.1.3", 77 | "chai": "^6.2.1", 78 | "coveradge": "^0.8.2", 79 | "eslint": "^9.39.1", 80 | "eslint-config-ash-nazg": "^39.8.0", 81 | "http-server": "^14.1.1", 82 | "license-badger": "^0.22.1", 83 | "mocha": "^11.7.5", 84 | "mocha-badge-generator": "^0.11.0", 85 | "mocha-multi-reporters": "^1.5.1", 86 | "open-cli": "^8.0.0", 87 | "rollup": "4.53.2", 88 | "typedoc": "^0.28.14", 89 | "typescript": "^5.9.3" 90 | }, 91 | "keywords": [ 92 | "json", 93 | "jsonpath" 94 | ], 95 | "browserslist": [ 96 | "defaults, not op_mini all" 97 | ], 98 | "c8": { 99 | "reporter": [ 100 | "text", 101 | "html", 102 | "json-summary" 103 | ], 104 | "exclude": [ 105 | ".mocharc.cjs", 106 | "eslint.config.js", 107 | "src/jsonpath.d.ts", 108 | "rollup.config.js", 109 | ".idea", 110 | "coverage", 111 | "dist", 112 | "demo", 113 | "docs", 114 | "ignore", 115 | "test", 116 | "test-helpers" 117 | ] 118 | }, 119 | "scripts": { 120 | "prepublishOnly": "pnpm i", 121 | "license-badge": "license-badger --corrections --uncategorizedLicenseTemplate \"\\${license} (\\${name} (\\${version}))\" --filteredTypes=nonempty --textTemplate \"License types\n(project, deps, and bundled devDeps)\" --packageJson --production badges/licenses-badge.svg", 122 | "license-badge-dev": "license-badger --corrections --filteredTypes=nonempty --textTemplate \"License types\n(all devDeps)\" --allDevelopment badges/licenses-badge-dev.svg", 123 | "license-badges": "npm run license-badge && npm run license-badge-dev", 124 | "build-docs": "typedoc --out docs/ts src --excludeExternals --entryPointStrategy Expand", 125 | "open-docs": "open-cli http://localhost:8084/docs/ts/ && npm start", 126 | "coverage": "open-cli http://localhost:8084/coverage/ && npm start", 127 | "coverage-badge": "coveradge badges/coverage-badge.svg", 128 | "node-import-test": "node --experimental-modules demo/node-import-test.mjs", 129 | "open": "open-cli http://localhost:8084/demo/ && npm start", 130 | "start": "http-server -p 8084", 131 | "cli": "./bin/jsonpath-cli.js package.json name", 132 | "typescript": "tsc", 133 | "mocha": "mocha --require test-helpers/node-env.js --reporter-options configFile=mocha-multi-reporters.json test", 134 | "c8": "rm -Rf ./coverage && rm -Rf ./node_modules/.cache && c8 --all npm run mocha && npm run coverage-badge", 135 | "rollup": "rollup -c", 136 | "eslint": "eslint .", 137 | "lint": "npm run eslint", 138 | "test": "npm run eslint && npm run rollup && npm run c8 && npm run typescript", 139 | "browser-test": "npm run eslint && npm run rollup && open-cli http://localhost:8084/test/ && npm start" 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /test/test.callback.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - Callback', function () { 3 | const json = { 4 | "store": { 5 | "book": [{ 6 | "category": "reference", 7 | "author": "Nigel Rees", 8 | "title": "Sayings of the Century", 9 | "price": 8.95 10 | }, 11 | { 12 | "category": "fiction", 13 | "author": "Evelyn Waugh", 14 | "title": "Sword of Honour", 15 | "price": 12.99 16 | }, 17 | { 18 | "category": "fiction", 19 | "author": "Herman Melville", 20 | "title": "Moby Dick", 21 | "isbn": "0-553-21311-3", 22 | "price": 8.99 23 | }, 24 | { 25 | "category": "fiction", 26 | "author": "J. R. R. Tolkien", 27 | "title": "The Lord of the Rings", 28 | "isbn": "0-395-19395-8", 29 | "price": 22.99 30 | }], 31 | "bicycle": { 32 | "color": "red", 33 | "price": 19.95 34 | } 35 | } 36 | }; 37 | 38 | it('Callback', () => { 39 | const expected = ['value', json.store.bicycle, {path: "$['store']['bicycle']", value: json.store.bicycle, parent: json.store, parentProperty: 'bicycle', hasArrExpr: undefined}]; 40 | let result; 41 | /** 42 | * 43 | * @param {PlainObject} data 44 | * @param {string} type 45 | * @param {PlainObject} fullData 46 | * @returns {void} 47 | */ 48 | function callback (data, type, fullData) { 49 | if (!result) { 50 | result = []; 51 | } 52 | result.push(type, data, fullData); 53 | } 54 | jsonpath({json, path: '$.store.bicycle', resultType: 'value', wrap: false, callback}); 55 | assert.deepEqual(result, expected); 56 | }); 57 | 58 | it('Callback with `resultType`: "all"', () => { 59 | const expected = [ 60 | 'value', 61 | { 62 | path: "$['store']['bicycle']", 63 | value: json.store.bicycle, 64 | parent: json.store, 65 | parentProperty: 'bicycle', 66 | pointer: '/store/bicycle', 67 | hasArrExpr: undefined 68 | }, 69 | { 70 | path: "$['store']['bicycle']", 71 | value: json.store.bicycle, 72 | parent: json.store, 73 | parentProperty: 'bicycle', 74 | pointer: '/store/bicycle', 75 | hasArrExpr: undefined 76 | } 77 | ]; 78 | let result; 79 | /** 80 | * 81 | * @param {PlainObject} data 82 | * @param {string} type 83 | * @param {PlainObject} fullData 84 | * @returns {void} 85 | */ 86 | function callback (data, type, fullData) { 87 | if (!result) { 88 | result = []; 89 | } 90 | result.push(type, data, fullData); 91 | } 92 | jsonpath({json, path: '$.store.bicycle', resultType: 'all', wrap: false, callback}); 93 | assert.deepEqual(result[0], expected[0]); 94 | assert.deepEqual(result, expected); 95 | }); 96 | 97 | // https://github.com/s3u/JSONPath/issues/126 98 | it('Using callback to set', function () { 99 | const expected = { 100 | age: 30, 101 | email: 'abc@example.com', 102 | 'something_deeper': { 103 | abc: 1, 104 | quantity: 11 105 | }, 106 | firstName: 'John', 107 | lastName: 'Doe' 108 | }; 109 | const givenPerson = { 110 | age: 30, 111 | email: 'abc@example.com', 112 | // let's add firstName, lastName fields 113 | 'something_deeper': { 114 | abc: 1 115 | // let's add quantity here 116 | } 117 | }; 118 | 119 | // defined an object with "json_path":"value" format, 120 | // made sure it is not a deep object. 121 | const obj1 = { 122 | $: { 123 | 'firstName': 'John', 124 | 'lastName': 'Doe' 125 | }, 126 | '$.something_deeper': { 127 | quantity: 11 128 | } 129 | }; 130 | 131 | Object.entries(obj1).forEach(([path, valuesToSet]) => { 132 | jsonpath({ 133 | json: givenPerson, 134 | path, 135 | wrap: false, 136 | callback (obj) { 137 | Object.entries(valuesToSet).forEach(([key, val]) => { 138 | obj[key] = val; 139 | }); 140 | } 141 | }); 142 | }); 143 | const result = givenPerson; 144 | assert.deepEqual(result, expected); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /docs/ts/types/JSONPathOtherTypeCallback.html: -------------------------------------------------------------------------------- 1 | JSONPathOtherTypeCallback | jsonpath-plusPreparing search index...The search index is not availablejsonpath-plusjsonpath-plusJSONPathOtherTypeCallbackType Alias JSONPathOtherTypeCallbackJSONPathOtherTypeCallback: (...args: any[]) => voidType declaration(...args: any[]): voidParameters...args: any[]Returns voidSettingsMember VisibilityProtectedInheritedThemeOSLightDarkjsonpath-plusLoading... 2 | -------------------------------------------------------------------------------- /docs/ts/types/JSONPathCallback.html: -------------------------------------------------------------------------------- 1 | JSONPathCallback | jsonpath-plusPreparing search index...The search index is not availablejsonpath-plusjsonpath-plusJSONPathCallbackType Alias JSONPathCallbackJSONPathCallback: (payload: any, payloadType: any, fullPayload: any) => anyType declaration(payload: any, payloadType: any, fullPayload: any): anyParameterspayload: anypayloadType: anyfullPayload: anyReturns anySettingsMember VisibilityProtectedInheritedThemeOSLightDarkjsonpath-plusLoading... 2 | -------------------------------------------------------------------------------- /test/test.type-operators.js: -------------------------------------------------------------------------------- 1 | 2 | describe('JSONPath - Type Operators', function () { 3 | // tests based on examples at http://goessner.net/articles/jsonpath/ 4 | 5 | const json = {"store": { 6 | "book": [ 7 | { 8 | "category": "reference", 9 | "author": "Nigel Rees", 10 | "title": "Sayings of the Century", 11 | "price": [8.95, 8.94, 8.93] 12 | }, 13 | { 14 | "category": "fiction", 15 | "author": "Evelyn Waugh", 16 | "title": "Sword of Honour", 17 | "price": 12.99 18 | }, 19 | { 20 | "category": "fiction", 21 | "author": "Herman Melville", 22 | "title": "Moby Dick", 23 | "isbn": "0-553-21311-3", 24 | "price": 8.99 25 | }, 26 | { 27 | "category": "fiction", 28 | "author": "J. R. R. Tolkien", 29 | "title": "The Lord of the Rings", 30 | "isbn": "0-395-19395-8", 31 | "price": 22.99 32 | } 33 | ], 34 | "bicycle": { 35 | "color": "red", 36 | "price": 19.95 37 | } 38 | }}; 39 | 40 | it('@number()', () => { 41 | const expected = [8.95, 8.94, 8.93, 12.99, 8.99, 22.99]; 42 | const result = jsonpath({json, path: '$.store.book..*@number()', flatten: true}); 43 | assert.deepEqual(result, expected); 44 | }); 45 | 46 | it('@scalar()', () => { 47 | const expected = ["red", 19.95]; 48 | const result = jsonpath({json, path: '$.store.bicycle..*@scalar()', flatten: true}); 49 | assert.deepEqual(result, expected); 50 | }); 51 | 52 | it('@scalar() get falsey and avoid objects', () => { 53 | const jsonMixed = { 54 | nested: { 55 | a: 5, 56 | b: {}, 57 | c: null, 58 | d: 'abc' 59 | } 60 | }; 61 | const expected = [ 62 | jsonMixed.nested.a, jsonMixed.nested.c, jsonMixed.nested.d 63 | ]; 64 | const result = jsonpath({json: jsonMixed, path: '$..*@scalar()'}); 65 | assert.deepEqual(result, expected); 66 | }); 67 | 68 | it('@other()', () => { 69 | const expected = [12.99, 8.99, 22.99]; 70 | 71 | /** 72 | * @typedef {any} Value 73 | */ 74 | /** 75 | * 76 | * @param {Value} val 77 | * @returns {boolean} 78 | */ 79 | function endsIn99 (val /* , path, parent, parentPropName */) { 80 | return Boolean((/\.99/u).test(val.toString())); 81 | } 82 | const result = jsonpath({json, path: '$.store.book..*@other()', flatten: true, otherTypeCallback: endsIn99}); 83 | assert.deepEqual(result, expected); 84 | }); 85 | 86 | it('throw with `@other` and no `otherTypeCallback`', function () { 87 | expect(() => { 88 | jsonpath({ 89 | json: {a: new Date()}, path: '$..*@other()' 90 | }); 91 | }).to.throw( 92 | TypeError, 93 | 'You must supply an otherTypeCallback callback option ' + 94 | 'with the @other() operator.' 95 | ); 96 | }); 97 | 98 | it('@object()', () => { 99 | const jsonMixed = { 100 | nested: { 101 | a: true, 102 | b: null, 103 | c: { 104 | d: 7 105 | } 106 | } 107 | }; 108 | const expected = [jsonMixed.nested, jsonMixed.nested.c]; 109 | const result = jsonpath({ 110 | json: jsonMixed, path: '$..*@object()', flatten: true 111 | }); 112 | assert.deepEqual(result, expected); 113 | }); 114 | 115 | it('@array()', () => { 116 | const jsonMixed = { 117 | nested: { 118 | a: [3, 4, 5], 119 | b: null, 120 | c: [ 121 | 7, [8, 9] 122 | ] 123 | } 124 | }; 125 | const expected = [ 126 | jsonMixed.nested.a, jsonMixed.nested.c, jsonMixed.nested.c[1] 127 | ]; 128 | const result = jsonpath({ 129 | json: jsonMixed, path: '$..*@array()' 130 | }); 131 | assert.deepEqual(result, expected); 132 | }); 133 | 134 | it('@boolean()', () => { 135 | const jsonMixed = { 136 | nested: { 137 | a: true, 138 | b: null, 139 | c: [ 140 | 7, [false, 9] 141 | ] 142 | } 143 | }; 144 | const expected = [jsonMixed.nested.a, jsonMixed.nested.c[1][0]]; 145 | const result = jsonpath({ 146 | json: jsonMixed, path: '$..*@boolean()', flatten: true 147 | }); 148 | assert.deepEqual(result, expected); 149 | }); 150 | 151 | it('@integer()', () => { 152 | const jsonMixed = { 153 | nested: { 154 | a: 50.7, 155 | b: null, 156 | c: [ 157 | 42, [false, 73] 158 | ] 159 | } 160 | }; 161 | const expected = [jsonMixed.nested.c[0], jsonMixed.nested.c[1][1]]; 162 | const result = jsonpath({ 163 | json: jsonMixed, path: '$..*@integer()', flatten: true 164 | }); 165 | assert.deepEqual(result, expected); 166 | }); 167 | 168 | it('@nonFinite()', () => { 169 | const jsonMixed = { 170 | nested: { 171 | a: 50.7, 172 | b: Number.NEGATIVE_INFINITY, 173 | c: [ 174 | 42, [Number.POSITIVE_INFINITY, 73, Number.NaN] 175 | ] 176 | } 177 | }; 178 | const expected = [ 179 | jsonMixed.nested.b, jsonMixed.nested.c[1][0], jsonMixed.nested.c[1][2] 180 | ]; 181 | const result = jsonpath({ 182 | json: jsonMixed, path: '$..*@nonFinite()' 183 | }); 184 | assert.deepEqual(result, expected); 185 | }); 186 | 187 | it('@null()', () => { 188 | const jsonMixed = { 189 | nested: { 190 | a: 50.7, 191 | b: null, 192 | c: [ 193 | 42, [false, 73] 194 | ] 195 | } 196 | }; 197 | const expected = [null]; 198 | const result = jsonpath({ 199 | json: jsonMixed, path: '$..*@null()' 200 | }); 201 | assert.deepEqual(result, expected); 202 | }); 203 | }); 204 | -------------------------------------------------------------------------------- /test/test.nested_expression.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase -- Convenient */ 2 | import {checkBuiltInVMAndNodeVM} from "../test-helpers/checkVM.js"; 3 | 4 | checkBuiltInVMAndNodeVM(function (vmType, setBuiltInState) { 5 | describe(`JSONPath - Nested Expressions (${vmType})`, function () { 6 | before(setBuiltInState); 7 | 8 | it("nested filter expression to select parent via matching on nested child", () => { 9 | const json = { 10 | name: "root", 11 | children: [ 12 | { 13 | name: "child1", 14 | grand_children: [{name: "child1_1"}, {name: "child1_2"}] 15 | }, 16 | {name: "child2", grand_children: [{name: "child2_1"}]}, 17 | { 18 | name: "child3", 19 | grand_children: [{name: "child3_1"}, {name: "child3_2"}] 20 | } 21 | ] 22 | }; 23 | const result = jsonpath({ 24 | json, 25 | path: "$.children[?(@.grand_children[?(@.name=='child2_1')])]", 26 | resultType: "all" 27 | }); 28 | assert.deepEqual(result, [ 29 | { 30 | path: "$['children'][1]", 31 | value: json.children[1], 32 | parent: json.children, 33 | parentProperty: 1, 34 | hasArrExpr: true, 35 | pointer: "/children/1" 36 | } 37 | ]); 38 | }); 39 | 40 | it("nested filter expression that also has a nested filter expression", () => { 41 | const json = [{ 42 | name: "grand_parent_a", 43 | children: [ 44 | { 45 | name: "child1a", 46 | grand_children: [{name: "child1_1a"}, {name: "child1_2a"}] 47 | }, 48 | {name: "child2a", grand_children: [{name: "child2_1a"}]}, 49 | { 50 | name: "child3a", 51 | grand_children: [{name: "child3_1a"}, {name: "child3_2a"}] 52 | } 53 | ] 54 | }, { 55 | name: "grand_parent_b", 56 | children: [ 57 | { 58 | name: "child1b", 59 | grand_children: [{name: "child1_1b"}, {name: "child1_2b"}] 60 | }, 61 | {name: "child2b", grand_children: [{name: "child2_1b"}]}, 62 | { 63 | name: "child3b", 64 | grand_children: [{name: "child3_1b"}, {name: "child3_2b"}] 65 | } 66 | ] 67 | }]; 68 | const result = jsonpath({ 69 | json, 70 | path: "$[?(@.children[?(@.grand_children[?(@.name=='child2_1b')])])]", 71 | flatten: true, 72 | resultType: "all" 73 | }); 74 | assert.deepEqual(result, [ 75 | { 76 | path: "$[1]", 77 | value: json[1], 78 | parent: json, 79 | parentProperty: 1, 80 | hasArrExpr: true, 81 | pointer: "/1" 82 | } 83 | ]); 84 | }); 85 | 86 | it("nested filter expression (4 levels)", () => { 87 | const json = [{ 88 | a: [{ 89 | b: [{ 90 | c: [{ 91 | d: [{e: 1}] 92 | }] 93 | }] 94 | }] 95 | }, { 96 | a: [{ 97 | b: [{ 98 | c: [{ 99 | d: [{e: 2}] 100 | }] 101 | }] 102 | }] 103 | }, 104 | { 105 | a: [{ 106 | b: [{ 107 | c: [{ 108 | d: [{e: 3}] 109 | }] 110 | }] 111 | }] 112 | }]; 113 | const result = jsonpath({ 114 | json, 115 | path: "$[?(@.a[?(@.b[?(@.c[?(@.d[?(@.e==2)])])])])]", 116 | flatten: true, 117 | resultType: "all" 118 | }); 119 | assert.deepEqual(result, [ 120 | { 121 | path: "$[1]", 122 | value: json[1], 123 | parent: json, 124 | parentProperty: 1, 125 | hasArrExpr: true, 126 | pointer: "/1" 127 | } 128 | ]); 129 | }); 130 | 131 | it("filter expression with subfilter (json-path-comparison)", () => { 132 | const json = [ 133 | { 134 | a: [{price: 1}, {price: 3}] 135 | }, 136 | { 137 | a: [{price: 11}] 138 | }, 139 | { 140 | a: [{price: 8}, {price: 12}, {price: 3}] 141 | }, 142 | { 143 | a: [] 144 | } 145 | ]; 146 | const result = jsonpath({ 147 | json, 148 | path: "$[?(@.a[?(@.price>10)])]", 149 | resultType: "all" 150 | }); 151 | assert.deepEqual(result, [ 152 | { 153 | path: "$[1]", 154 | value: json[1], 155 | parent: json, 156 | parentProperty: 1, 157 | hasArrExpr: true, 158 | pointer: "/1" 159 | }, 160 | { 161 | path: "$[2]", 162 | value: json[2], 163 | parent: json, 164 | parentProperty: 2, 165 | hasArrExpr: true, 166 | pointer: "/2" 167 | } 168 | ]); 169 | }); 170 | 171 | it("draft ietf jsonpath (base 21) nested filter example", () => { 172 | const json = { 173 | "a": [3, 5, 1, 2, 4, 6, 174 | {"b": "j"}, 175 | {"b": "k"}, 176 | {"b": {}}, 177 | {"b": "kilo"}], 178 | "o": {"p": 1, "q": 2, "r": 3, "s": 5, "t": {"u": 6}}, 179 | "e": "f" 180 | }; 181 | const result = jsonpath({ 182 | json, 183 | path: "$[?(@[?(@.b)])]", 184 | resultType: "all" 185 | }); 186 | assert.deepEqual(result, [ 187 | { 188 | path: "$['a']", 189 | value: json.a, 190 | parent: json, 191 | parentProperty: 'a', 192 | hasArrExpr: true, 193 | pointer: "/a" 194 | } 195 | ]); 196 | }); 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /src/Safe-Script.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-bitwise -- Convenient */ 2 | import jsep from 'jsep'; 3 | import jsepRegex from '@jsep-plugin/regex'; 4 | import jsepAssignment from '@jsep-plugin/assignment'; 5 | 6 | // register plugins 7 | jsep.plugins.register(jsepRegex, jsepAssignment); 8 | jsep.addUnaryOp('typeof'); 9 | jsep.addUnaryOp('void'); 10 | jsep.addLiteral('null', null); 11 | jsep.addLiteral('undefined', undefined); 12 | 13 | const BLOCKED_PROTO_PROPERTIES = new Set([ 14 | 'constructor', 15 | '__proto__', 16 | '__defineGetter__', 17 | '__defineSetter__' 18 | ]); 19 | 20 | const SafeEval = { 21 | /** 22 | * @param {jsep.Expression} ast 23 | * @param {Record} subs 24 | */ 25 | evalAst (ast, subs) { 26 | switch (ast.type) { 27 | case 'BinaryExpression': 28 | case 'LogicalExpression': 29 | return SafeEval.evalBinaryExpression(ast, subs); 30 | case 'Compound': 31 | return SafeEval.evalCompound(ast, subs); 32 | case 'ConditionalExpression': 33 | return SafeEval.evalConditionalExpression(ast, subs); 34 | case 'Identifier': 35 | return SafeEval.evalIdentifier(ast, subs); 36 | case 'Literal': 37 | return SafeEval.evalLiteral(ast, subs); 38 | case 'MemberExpression': 39 | return SafeEval.evalMemberExpression(ast, subs); 40 | case 'UnaryExpression': 41 | return SafeEval.evalUnaryExpression(ast, subs); 42 | case 'ArrayExpression': 43 | return SafeEval.evalArrayExpression(ast, subs); 44 | case 'CallExpression': 45 | return SafeEval.evalCallExpression(ast, subs); 46 | case 'AssignmentExpression': 47 | return SafeEval.evalAssignmentExpression(ast, subs); 48 | default: 49 | throw SyntaxError('Unexpected expression', ast); 50 | } 51 | }, 52 | evalBinaryExpression (ast, subs) { 53 | const result = { 54 | '||': (a, b) => a || b(), 55 | '&&': (a, b) => a && b(), 56 | '|': (a, b) => a | b(), 57 | '^': (a, b) => a ^ b(), 58 | '&': (a, b) => a & b(), 59 | // eslint-disable-next-line eqeqeq -- API 60 | '==': (a, b) => a == b(), 61 | // eslint-disable-next-line eqeqeq -- API 62 | '!=': (a, b) => a != b(), 63 | '===': (a, b) => a === b(), 64 | '!==': (a, b) => a !== b(), 65 | '<': (a, b) => a < b(), 66 | '>': (a, b) => a > b(), 67 | '<=': (a, b) => a <= b(), 68 | '>=': (a, b) => a >= b(), 69 | '<<': (a, b) => a << b(), 70 | '>>': (a, b) => a >> b(), 71 | '>>>': (a, b) => a >>> b(), 72 | '+': (a, b) => a + b(), 73 | '-': (a, b) => a - b(), 74 | '*': (a, b) => a * b(), 75 | '/': (a, b) => a / b(), 76 | '%': (a, b) => a % b() 77 | }[ast.operator]( 78 | SafeEval.evalAst(ast.left, subs), 79 | () => SafeEval.evalAst(ast.right, subs) 80 | ); 81 | return result; 82 | }, 83 | evalCompound (ast, subs) { 84 | let last; 85 | for (let i = 0; i < ast.body.length; i++) { 86 | if ( 87 | ast.body[i].type === 'Identifier' && 88 | ['var', 'let', 'const'].includes(ast.body[i].name) && 89 | ast.body[i + 1] && 90 | ast.body[i + 1].type === 'AssignmentExpression' 91 | ) { 92 | // var x=2; is detected as 93 | // [{Identifier var}, {AssignmentExpression x=2}] 94 | // eslint-disable-next-line @stylistic/max-len -- Long 95 | // eslint-disable-next-line sonarjs/updated-loop-counter -- Convenient 96 | i += 1; 97 | } 98 | const expr = ast.body[i]; 99 | last = SafeEval.evalAst(expr, subs); 100 | } 101 | return last; 102 | }, 103 | evalConditionalExpression (ast, subs) { 104 | if (SafeEval.evalAst(ast.test, subs)) { 105 | return SafeEval.evalAst(ast.consequent, subs); 106 | } 107 | return SafeEval.evalAst(ast.alternate, subs); 108 | }, 109 | evalIdentifier (ast, subs) { 110 | if (Object.hasOwn(subs, ast.name)) { 111 | return subs[ast.name]; 112 | } 113 | throw ReferenceError(`${ast.name} is not defined`); 114 | }, 115 | evalLiteral (ast) { 116 | return ast.value; 117 | }, 118 | evalMemberExpression (ast, subs) { 119 | const prop = String( 120 | // NOTE: `String(value)` throws error when 121 | // value has overwritten the toString method to return non-string 122 | // i.e. `value = {toString: () => []}` 123 | ast.computed 124 | ? SafeEval.evalAst(ast.property) // `object[property]` 125 | : ast.property.name // `object.property` property is Identifier 126 | ); 127 | const obj = SafeEval.evalAst(ast.object, subs); 128 | if (obj === undefined || obj === null) { 129 | throw TypeError( 130 | `Cannot read properties of ${obj} (reading '${prop}')` 131 | ); 132 | } 133 | if (!Object.hasOwn(obj, prop) && BLOCKED_PROTO_PROPERTIES.has(prop)) { 134 | throw TypeError( 135 | `Cannot read properties of ${obj} (reading '${prop}')` 136 | ); 137 | } 138 | const result = obj[prop]; 139 | if (typeof result === 'function') { 140 | return result.bind(obj); // arrow functions aren't affected by bind. 141 | } 142 | return result; 143 | }, 144 | evalUnaryExpression (ast, subs) { 145 | const result = { 146 | '-': (a) => -SafeEval.evalAst(a, subs), 147 | '!': (a) => !SafeEval.evalAst(a, subs), 148 | '~': (a) => ~SafeEval.evalAst(a, subs), 149 | // eslint-disable-next-line no-implicit-coercion -- API 150 | '+': (a) => +SafeEval.evalAst(a, subs), 151 | typeof: (a) => typeof SafeEval.evalAst(a, subs), 152 | // eslint-disable-next-line no-void, sonarjs/void-use -- feature 153 | void: (a) => void SafeEval.evalAst(a, subs) 154 | }[ast.operator](ast.argument); 155 | return result; 156 | }, 157 | evalArrayExpression (ast, subs) { 158 | return ast.elements.map((el) => SafeEval.evalAst(el, subs)); 159 | }, 160 | evalCallExpression (ast, subs) { 161 | const args = ast.arguments.map((arg) => SafeEval.evalAst(arg, subs)); 162 | const func = SafeEval.evalAst(ast.callee, subs); 163 | // if (func === Function) { 164 | // throw new Error('Function constructor is disabled'); 165 | // } 166 | return func(...args); 167 | }, 168 | evalAssignmentExpression (ast, subs) { 169 | if (ast.left.type !== 'Identifier') { 170 | throw SyntaxError('Invalid left-hand side in assignment'); 171 | } 172 | const id = ast.left.name; 173 | const value = SafeEval.evalAst(ast.right, subs); 174 | subs[id] = value; 175 | return subs[id]; 176 | } 177 | }; 178 | 179 | /** 180 | * A replacement for NodeJS' VM.Script which is also {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP | Content Security Policy} friendly. 181 | */ 182 | class SafeScript { 183 | /** 184 | * @param {string} expr Expression to evaluate 185 | */ 186 | constructor (expr) { 187 | this.code = expr; 188 | this.ast = jsep(this.code); 189 | } 190 | 191 | /** 192 | * @param {object} context Object whose items will be added 193 | * to evaluation 194 | * @returns {EvaluatedResult} Result of evaluated code 195 | */ 196 | runInNewContext (context) { 197 | // `Object.create(null)` creates a prototypeless object 198 | const keyMap = Object.assign(Object.create(null), context); 199 | return SafeEval.evalAst(this.ast, keyMap); 200 | } 201 | } 202 | 203 | export {SafeScript}; 204 | -------------------------------------------------------------------------------- /src/jsonpath.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Declaration for https://github.com/s3u/JSONPath 3 | */ 4 | declare module 'jsonpath-plus' { 5 | type JSONPathCallback = ( 6 | payload: any, payloadType: any, fullPayload: any 7 | ) => any 8 | 9 | type JSONPathOtherTypeCallback = (...args: any[]) => void 10 | 11 | class EvalClass { 12 | constructor(code: string); 13 | runInNewContext(context: object): any; 14 | } 15 | 16 | interface JSONPathOptions { 17 | /** 18 | * The JSONPath expression as a (normalized or unnormalized) string or 19 | * array. 20 | */ 21 | path: string | any[] 22 | /** 23 | * The JSON object to evaluate (whether of null, boolean, number, 24 | * string, object, or array type). 25 | */ 26 | json: null | boolean | number | string | object | any[] 27 | /** 28 | * If this is supplied as false, one may call the evaluate method 29 | * manually. 30 | * 31 | * @default true 32 | */ 33 | autostart?: true | boolean 34 | /** 35 | * Whether the returned array of results will be flattened to a 36 | * single dimension array. 37 | * 38 | * @default false 39 | */ 40 | flatten?: false | boolean 41 | /** 42 | * Can be case-insensitive form of "value", "path", "pointer", "parent", 43 | * or "parentProperty" to determine respectively whether to return 44 | * results as the values of the found items, as their absolute paths, 45 | * as JSON Pointers to the absolute paths, as their parent objects, 46 | * or as their parent's property name. 47 | * 48 | * If set to "all", all of these types will be returned on an object with 49 | * the type as key name. 50 | * 51 | * @default 'value' 52 | */ 53 | resultType?: 54 | 'value' | 'path' | 'pointer' | 'parent' | 'parentProperty' | 'all' 55 | 56 | /** 57 | * Key-value map of variables to be available to code evaluations such 58 | * as filtering expressions. 59 | * (Note that the current path and value will also be available to those 60 | * expressions; see the Syntax section for details.) 61 | */ 62 | sandbox?: Map 63 | /** 64 | * Whether or not to wrap the results in an array. 65 | * 66 | * If wrap is set to false, and no results are found, undefined will be 67 | * returned (as opposed to an empty array when wrap is set to true). 68 | * 69 | * If wrap is set to false and a single non-array result is found, that 70 | * result will be the only item returned (not within an array). 71 | * 72 | * An array will still be returned if multiple results are found, however. 73 | * To avoid ambiguities (in the case where it is necessary to distinguish 74 | * between a result which is a failure and one which is an empty array), 75 | * it is recommended to switch the default to false. 76 | * 77 | * @default true 78 | */ 79 | wrap?: true | boolean 80 | /** 81 | * Script evaluation method. 82 | * 83 | * `safe`: In browser, it will use a minimal scripting engine which doesn't 84 | * use `eval` or `Function` and satisfies Content Security Policy. In NodeJS, 85 | * it has no effect and is equivalent to native as scripting is safe there. 86 | * 87 | * `native`: uses the native scripting capabilities. i.e. unsafe `eval` or 88 | * `Function` in browser and `vm.Script` in nodejs. 89 | * 90 | * `true`: Same as 'safe' 91 | * 92 | * `false`: Disable Javascript executions in path string. Same as `preventEval: true` in previous versions. 93 | * 94 | * `callback [ (code, context) => value]`: A custom implementation which is called 95 | * with `code` and `context` as arguments to return the evaluated value. 96 | * 97 | * `class`: A class similar to nodejs vm.Script. It will be created with `code` as constructor argument and the code 98 | * is evaluated by calling `runInNewContext` with `context`. 99 | * 100 | * @default 'safe' 101 | */ 102 | eval?: 'safe' | 'native' | boolean | ((code: string, context: object) => any) | typeof EvalClass 103 | /** 104 | * Ignore errors while evaluating JSONPath expression. 105 | * 106 | * `true`: Don't break entire search if an error occurs while evaluating JSONPath expression on one key/value pair. 107 | * 108 | * `false`: Break entire search if an error occurs while evaluating JSONPath expression on one key/value pair. 109 | * 110 | * @default false 111 | * 112 | */ 113 | ignoreEvalErrors?: boolean 114 | /** 115 | * In the event that a query could be made to return the root node, 116 | * this allows the parent of that root node to be returned within results. 117 | * 118 | * @default null 119 | */ 120 | parent?: null | any 121 | /** 122 | * In the event that a query could be made to return the root node, 123 | * this allows the parentProperty of that root node to be returned within 124 | * results. 125 | * 126 | * @default null 127 | */ 128 | parentProperty?: null | any 129 | /** 130 | * If supplied, a callback will be called immediately upon retrieval of 131 | * an end point value. 132 | * 133 | * The three arguments supplied will be the value of the payload 134 | * (according to `resultType`), the type of the payload (whether it is 135 | * a normal "value" or a "property" name), and a full payload object 136 | * (with all `resultType`s). 137 | * 138 | * @default undefined 139 | */ 140 | callback?: undefined | JSONPathCallback 141 | /** 142 | * In the current absence of JSON Schema support, 143 | * one can determine types beyond the built-in types by adding the 144 | * perator `@other()` at the end of one's query. 145 | * 146 | * If such a path is encountered, the `otherTypeCallback` will be invoked 147 | * with the value of the item, its path, its parent, and its parent's 148 | * property name, and it should return a boolean indicating whether the 149 | * supplied value belongs to the "other" type or not (or it may handle 150 | * transformations and return false). 151 | * 152 | * @default undefined 153 | * 154 | */ 155 | otherTypeCallback?: undefined | JSONPathOtherTypeCallback 156 | } 157 | 158 | interface JSONPathOptionsAutoStart extends JSONPathOptions { 159 | autostart: false 160 | } 161 | 162 | interface JSONPathCallable { 163 | (options: JSONPathOptionsAutoStart): JSONPathClass 164 | (options: JSONPathOptions): T 165 | 166 | ( 167 | path: JSONPathOptions['path'], 168 | json: JSONPathOptions['json'], 169 | callback: JSONPathOptions['callback'], 170 | otherTypeCallback: JSONPathOptions['otherTypeCallback'] 171 | ): T 172 | } 173 | 174 | class JSONPathClass { 175 | /** 176 | * Exposes the cache object for those who wish to preserve and reuse 177 | * it for optimization purposes. 178 | */ 179 | cache: any 180 | 181 | /** 182 | * Accepts a normalized or unnormalized path as string and 183 | * converts to an array: for example, 184 | * `['$', 'aProperty', 'anotherProperty']`. 185 | */ 186 | toPathArray(path: string): string[] 187 | 188 | /** 189 | * Accepts a path array and converts to a normalized path string. 190 | * The string will be in a form like: 191 | * `$['aProperty']['anotherProperty][0]`. 192 | * The JSONPath terminal constructions `~` and `^` and type operators 193 | * like `@string()` are silently stripped. 194 | */ 195 | toPathString(path: string[]): string 196 | 197 | /** 198 | * Accepts a path array and converts to a JSON Pointer. 199 | * 200 | * The string will be in a form like: `/aProperty/anotherProperty/0` 201 | * (with any `~` and `/` internal characters escaped as per the JSON 202 | * Pointer spec). 203 | * 204 | * The JSONPath terminal constructions `~` and `^` and type operators 205 | * like `@string()` are silently stripped. 206 | */ 207 | toPointer(path: string[]): any 208 | 209 | evaluate( 210 | path: JSONPathOptions['path'], 211 | json: JSONPathOptions['json'], 212 | callback: JSONPathOptions['callback'], 213 | otherTypeCallback: JSONPathOptions['otherTypeCallback'] 214 | ): any 215 | evaluate(options: { 216 | path: JSONPathOptions['path'], 217 | json: JSONPathOptions['json'], 218 | callback: JSONPathOptions['callback'], 219 | otherTypeCallback: JSONPathOptions['otherTypeCallback'] 220 | }): any 221 | } 222 | 223 | type JSONPathType = JSONPathCallable & JSONPathClass 224 | 225 | export const JSONPath: JSONPathType 226 | } 227 | -------------------------------------------------------------------------------- /test/test.eval.js: -------------------------------------------------------------------------------- 1 | import {checkBuiltInVMAndNodeVM} from '../test-helpers/checkVM.js'; 2 | 3 | checkBuiltInVMAndNodeVM(function (vmType, setBuiltInState) { 4 | describe(`JSONPath - Eval (${vmType} - native)`, function () { 5 | before(setBuiltInState); 6 | const json = { 7 | "store": { 8 | "book": { 9 | "category": "reference", 10 | "author": "Nigel Rees", 11 | "title": "Sayings of the Century", 12 | "price": [8.95, 8.94] 13 | }, 14 | "books": [{ 15 | "category": "fiction", 16 | "author": "Evelyn Waugh", 17 | "title": "Sword of Honour", 18 | "price": [10.99, 12.29] 19 | }, { 20 | "category": "fiction", 21 | "author": "Herman Melville", 22 | "title": "Moby Dick", 23 | "isbn": "0-553-21311-3", 24 | "price": [8.99, 6.95] 25 | }] 26 | } 27 | }; 28 | 29 | it('fail if eval is unsupported', () => { 30 | expect(() => { 31 | jsonpath({json, path: "$..[?(@.category === category)]", eval: 'wrong-eval'}); 32 | }).to.throw( 33 | TypeError, 34 | 'Unknown "eval" property "wrong-eval"' 35 | ); 36 | }); 37 | 38 | it('multi statement eval', () => { 39 | const expected = [json.store.books[0]]; 40 | const selector = '$..[?(' + 41 | 'var sum = @.price && @.price[0]+@.price[1];' + 42 | 'sum > 20;)]'; 43 | const result = jsonpath({json, path: selector, wrap: false, eval: 'native'}); 44 | assert.deepEqual(result, expected); 45 | }); 46 | it('multi statement eval (with use strict)', () => { 47 | const expected = [json.store.books[0]]; 48 | const selector = '$..[?(' + 49 | '"use strict";' + 50 | 'var sum = @.price && @.price[0]+@.price[1];' + 51 | 'sum > 20;)]'; 52 | const result = jsonpath({json, path: selector, wrap: false, 53 | eval: 'native'}); 54 | assert.deepEqual(result, expected); 55 | }); 56 | 57 | it('accessing current path', () => { 58 | const expected = [json.store.books[1]]; 59 | const result = jsonpath({json, 60 | path: "$..[?(@path==\"$['store']['books'][1]\")]", 61 | wrap: false, 62 | eval: 'native' 63 | }); 64 | assert.deepEqual(result, expected); 65 | }); 66 | 67 | it('sandbox', () => { 68 | const expected = [json.store.book]; 69 | const result = jsonpath({ 70 | json, 71 | sandbox: {category: 'reference'}, 72 | path: "$..[?(@.category === category)]", wrap: false, 73 | eval: 'native' 74 | }); 75 | assert.deepEqual(result, expected); 76 | }); 77 | 78 | it('sandbox (with `arguments`)', () => { 79 | const expected = [json.store.book]; 80 | const selector = "$..[?(@.category === arguments)]"; 81 | const result = jsonpath({ 82 | json, 83 | path: selector, 84 | sandbox: { 85 | arguments: 'reference' 86 | }, 87 | wrap: false, 88 | eval: 'native' 89 | }); 90 | assert.deepEqual(result, expected); 91 | }); 92 | 93 | it('sandbox with function without "function" in string', () => { 94 | const expected = [json.store.book]; 95 | const result = jsonpath({ 96 | json, 97 | sandbox: { 98 | category () { 99 | return 'reference'; 100 | } 101 | }, 102 | path: "$..[?(@.category === category())]", wrap: false, 103 | eval: 'native' 104 | }); 105 | assert.deepEqual(result, expected); 106 | }); 107 | 108 | it('sandbox with function with "function" in string', () => { 109 | const expected = [json.store.book]; 110 | const result = jsonpath({ 111 | json, 112 | sandbox: { 113 | category () { 114 | return 'reference'; 115 | } 116 | }, 117 | path: "$..[?(@.category === category())]", wrap: false, 118 | eval: 'native' 119 | }); 120 | assert.deepEqual(result, expected); 121 | }); 122 | 123 | it('sandbox (with parsing function)', () => { 124 | const expected = [json.store.book]; 125 | const result = jsonpath({ 126 | json, 127 | sandbox: { 128 | filter (arg) { 129 | return arg.category === 'reference'; 130 | } 131 | }, 132 | path: "$..[?(filter(@))]", wrap: false, 133 | eval: 'native' 134 | }); 135 | assert.deepEqual(result, expected); 136 | }); 137 | 138 | describe('cyclic object', () => { 139 | // This is not an eval test, but we put it here for parity with item below 140 | it('cyclic object without a sandbox', () => { 141 | const circular = {a: {b: {c: 5}}}; 142 | circular.a.x = circular; 143 | const expected = circular.a.b; 144 | const result = jsonpath({ 145 | json: circular, 146 | path: '$.a.b', 147 | wrap: false, 148 | eval: 'native' 149 | }); 150 | assert.deepEqual(result, expected); 151 | }); 152 | it('cyclic object in a sandbox', () => { 153 | const circular = {category: 'fiction'}; 154 | circular.recurse = circular; 155 | const expected = json.store.books; 156 | const result = jsonpath({ 157 | json, 158 | path: '$..[?(@.category === aCircularReference.category)]', 159 | sandbox: { 160 | aCircularReference: circular 161 | }, 162 | wrap: false, 163 | eval: 'native' 164 | }); 165 | assert.deepEqual(result, expected); 166 | }); 167 | }); 168 | }); 169 | describe(`JSONPath - Eval (${vmType} - custom)`, function () { 170 | before(setBuiltInState); 171 | const json = { 172 | "store": { 173 | "book": { 174 | "category": "reference", 175 | "author": "Nigel Rees", 176 | "title": "Sayings of the Century", 177 | "price": [8.95, 8.94] 178 | }, 179 | "books": [{ 180 | "category": "fiction", 181 | "author": "Evelyn Waugh", 182 | "title": "Sword of Honour", 183 | "price": [10.99, 12.29] 184 | }, { 185 | "category": "fiction", 186 | "author": "Herman Melville", 187 | "title": "Moby Dick", 188 | "isbn": "0-553-21311-3", 189 | "price": [8.99, 6.95] 190 | }] 191 | } 192 | }; 193 | it('eval as callback function', () => { 194 | const evalCb = (code, ctxt) => { 195 | const script = new jsonpath.prototype.safeVm.Script(code); 196 | return script.runInNewContext(ctxt); 197 | }; 198 | const expected = [json.store.book]; 199 | const result = jsonpath({ 200 | json, 201 | path: '$..[?(@.category === "reference")]', 202 | eval: evalCb 203 | }); 204 | assert.deepEqual(result, expected); 205 | }); 206 | it('eval as class', () => { 207 | const expected = [json.store.book]; 208 | const result = jsonpath({ 209 | json, 210 | path: '$..[?(@.category === "reference")]', 211 | eval: jsonpath.prototype.safeVm.Script 212 | }); 213 | assert.deepEqual(result, expected); 214 | }); 215 | 216 | it('treat error as mismatch in eval', () => { 217 | const expected = [json.store.book]; 218 | const result = jsonpath({ 219 | json, 220 | path: '$..[?(@.category.toLowerCase() === "reference")]', 221 | eval: jsonpath.prototype.safeVm.Script, 222 | ignoreEvalErrors: true 223 | }); 224 | assert.deepEqual(result, expected); 225 | }); 226 | }); 227 | }); 228 | -------------------------------------------------------------------------------- /docs/ts/classes/EvalClass.html: -------------------------------------------------------------------------------- 1 | EvalClass | jsonpath-plusPreparing search index...The search index is not availablejsonpath-plusjsonpath-plusEvalClassClass EvalClass IndexConstructorsconstructor 2 | MethodsrunInNewContext 3 | Constructorsconstructornew EvalClass(code: string): EvalClassParameterscode: stringReturns EvalClass MethodsrunInNewContextrunInNewContext(context: object): anyParameterscontext: objectReturns anySettingsMember VisibilityProtectedInheritedThemeOSLightDarkOn This PageConstructorsconstructorMethodsrunInNewContextjsonpath-plusLoading... 4 | -------------------------------------------------------------------------------- /docs/ts/assets/icons.svg: -------------------------------------------------------------------------------- 1 | MMNEPVFCICPMFPCPTTAAATR -------------------------------------------------------------------------------- /test/test.examples.js: -------------------------------------------------------------------------------- 1 | import {checkBuiltInVMAndNodeVM} from '../test-helpers/checkVM.js'; 2 | 3 | checkBuiltInVMAndNodeVM(function (vmType, setBuiltInState) { 4 | describe(`JSONPath - Examples (${vmType})`, function () { 5 | before(setBuiltInState); 6 | // tests based on examples at http://goessner.net/articles/jsonpath/ 7 | const json = { 8 | "store": { 9 | "book": [{ 10 | "category": "reference", 11 | "author": "Nigel Rees", 12 | "title": "Sayings of the Century", 13 | "price": 8.95 14 | }, 15 | { 16 | "category": "fiction", 17 | "author": "Evelyn Waugh", 18 | "title": "Sword of Honour", 19 | "price": 12.99 20 | }, 21 | { 22 | "category": "fiction", 23 | "author": "Herman Melville", 24 | "title": "Moby Dick", 25 | "isbn": "0-553-21311-3", 26 | "price": 8.99 27 | }, 28 | { 29 | "category": "fiction", 30 | "author": "J. R. R. Tolkien", 31 | "title": "The Lord of the Rings", 32 | "isbn": "0-395-19395-8", 33 | "price": 22.99 34 | }], 35 | "bicycle": { 36 | "color": "red", 37 | "price": 19.95 38 | } 39 | } 40 | }; 41 | 42 | it('wildcards (with and without $.)', () => { 43 | const books = json.store.book; 44 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 45 | let result = jsonpath({json, path: '$.store.book[*].author'}); 46 | assert.deepEqual(result, expected); 47 | result = jsonpath({json, path: 'store.book[*].author'}); 48 | assert.deepEqual(result, expected); 49 | }); 50 | 51 | it('all properties, entire tree', () => { 52 | const books = json.store.book; 53 | const expected = [books[0].author, books[1].author, books[2].author, books[3].author]; 54 | const result = jsonpath({json, path: '$..author'}); 55 | assert.deepEqual(result, expected); 56 | }); 57 | 58 | it('all sub properties, single level', () => { 59 | const expected = [json.store.book, json.store.bicycle]; 60 | const result = jsonpath({json, path: '$.store.*'}); 61 | assert.deepEqual(result, expected); 62 | }); 63 | 64 | it('all sub properties, entire tree', () => { 65 | const books = json.store.book; 66 | const expected = [books[0].price, books[1].price, books[2].price, books[3].price, json.store.bicycle.price]; 67 | const result = jsonpath({json, path: '$.store..price'}); 68 | assert.deepEqual(result, expected); 69 | }); 70 | 71 | it('n property of entire tree', () => { 72 | const books = json.store.book; 73 | const expected = [books[2]]; 74 | const result = jsonpath({json, path: '$..book[2]'}); 75 | assert.deepEqual(result, expected); 76 | }); 77 | 78 | it('last property of entire tree', () => { 79 | const books = json.store.book; 80 | const expected = [books[3]]; 81 | let result = jsonpath({json, path: '$..book[(@.length-1)]'}); 82 | assert.deepEqual(result, expected); 83 | 84 | result = jsonpath({json, path: '$..book[-1:]'}); 85 | assert.deepEqual(result, expected); 86 | }); 87 | 88 | it('range of property of entire tree', () => { 89 | const books = json.store.book; 90 | const expected = [books[0], books[1]]; 91 | let result = jsonpath({json, path: '$..book[0,1]'}); 92 | assert.deepEqual(result, expected); 93 | 94 | result = jsonpath({json, path: '$..book[:2]'}); 95 | assert.deepEqual(result, expected); 96 | }); 97 | 98 | it('range of property of entire tree w/ single element result', () => { 99 | const book = json.store.book[0]; 100 | const input = {books: [book]}; 101 | const expected = [book]; 102 | let result = jsonpath({json: input, path: '$.books[0,1]', wrap: false}); 103 | assert.deepEqual(result, expected); 104 | 105 | result = jsonpath({json: input, path: '$.books[:1]', wrap: false}); 106 | assert.deepEqual(result, expected); 107 | }); 108 | 109 | it('categories and authors of all books', () => { 110 | const expected = ['reference', 'Nigel Rees']; 111 | const result = jsonpath({json, path: '$..book[0][category,author]'}); 112 | assert.deepEqual(result, expected); 113 | }); 114 | 115 | it('filter all properties if sub property exists, of entire tree', () => { 116 | const books = json.store.book; 117 | const expected = [books[2], books[3]]; 118 | const result = jsonpath({json, path: '$..book[?(@.isbn)]'}); 119 | assert.deepEqual(result, expected); 120 | }); 121 | 122 | it('filter all properties if sub property exists, of single element array', () => { 123 | const book = json.store.book[3]; 124 | const input = {books: [book]}; 125 | const expected = [book]; 126 | const result = jsonpath({json: input, path: '$.books[?(@.isbn)]', wrap: false}); 127 | assert.deepEqual(result, expected); 128 | }); 129 | 130 | it('filter all properties if sub property greater than of entire tree', () => { 131 | const books = json.store.book; 132 | const expected = [books[0], books[2]]; 133 | const result = jsonpath({json, path: '$..book[?(@.price<10)]'}); 134 | assert.deepEqual(result, expected); 135 | }); 136 | 137 | it('@ as a scalar value', () => { 138 | const expected = [json.store.bicycle.price, ...json.store.book.slice(1).map((book) => { 139 | return book.price; 140 | })]; 141 | const result = jsonpath({json, path: "$..*[?(@property === 'price' && @ !== 8.95)]", wrap: false}); 142 | assert.deepEqual(result, expected); 143 | }); 144 | 145 | it('all properties of a JSON structure (beneath the root)', () => { 146 | const expected = [ 147 | json.store, 148 | json.store.book, 149 | json.store.bicycle 150 | ]; 151 | json.store.book.forEach((book) => { 152 | expected.push(book); 153 | }); 154 | json.store.book.forEach(function (book) { 155 | Object.keys(book).forEach(function (p) { 156 | expected.push(book[p]); 157 | }); 158 | }); 159 | expected.push( 160 | json.store.bicycle.color, 161 | json.store.bicycle.price 162 | ); 163 | 164 | const result = jsonpath({json, path: '$..*'}); 165 | assert.deepEqual(result, expected); 166 | }); 167 | 168 | it('all parent components of a JSON structure', () => { 169 | const expected = [ 170 | json, 171 | json.store, 172 | json.store.book 173 | ]; 174 | json.store.book.forEach((book) => { 175 | expected.push(book); 176 | }); 177 | expected.push(json.store.bicycle); 178 | 179 | const result = jsonpath({json, path: '$..'}); 180 | assert.deepEqual(result, expected); 181 | }); 182 | 183 | it('root', () => { 184 | const expected = json; 185 | const result = jsonpath({json, path: '$', wrap: false}); 186 | assert.deepEqual(result, expected); 187 | }); 188 | 189 | it('Custom operator: parent (caret)', () => { 190 | const expected = [json.store, json.store.book]; 191 | const result = jsonpath({json, path: '$..[?(@.price>19)]^'}); 192 | assert.deepEqual(result, expected); 193 | }); 194 | it('Custom operator: property name (tilde)', () => { 195 | const expected = ['book', 'bicycle']; 196 | const result = jsonpath({json, path: '$.store.*~'}); 197 | assert.deepEqual(result, expected); 198 | }); 199 | it('Custom property @path', () => { 200 | const expected = json.store.book.slice(1); 201 | const result = jsonpath({json, path: '$.store.book[?(@path !== "$[\'store\'][\'book\'][0]")]'}); 202 | assert.deepEqual(result, expected); 203 | }); 204 | it('Custom property: @parent', () => { 205 | const expected = ['reference', 'fiction', 'fiction', 'fiction']; 206 | const result = jsonpath({json, path: '$..book[?(@parent.bicycle && @parent.bicycle.color === "red")].category'}); 207 | assert.deepEqual(result, expected); 208 | }); 209 | it('Custom property: @property', () => { 210 | let expected = json.store.book.reduce(function (arr, book) { 211 | arr.push(book.author, book.title); 212 | if (book.isbn) { 213 | arr.push(book.isbn); 214 | } 215 | arr.push(book.price); 216 | return arr; 217 | }, []); 218 | let result = jsonpath({json, path: '$..book.*[?(@property !== "category")]'}); 219 | assert.deepEqual(result, expected); 220 | 221 | expected = json.store.book.slice(1); 222 | result = jsonpath({json, path: '$..book[?(@property !== 0)]'}); 223 | assert.deepEqual(result, expected); 224 | }); 225 | it('Custom property: @parentProperty', () => { 226 | let expected = [json.store.bicycle.color, json.store.bicycle.price]; 227 | let result = jsonpath({json, path: '$.store.*[?(@parentProperty !== "book")]'}); 228 | assert.deepEqual(result, expected); 229 | 230 | expected = json.store.book.slice(1).reduce(function (rslt, book) { 231 | return [...rslt, ...Object.keys(book).reduce((reslt, prop) => { 232 | reslt.push(book[prop]); 233 | return reslt; 234 | }, [])]; 235 | }, []); 236 | result = jsonpath({json, path: '$..book.*[?(@parentProperty !== 0)]'}); 237 | assert.deepEqual(result, expected); 238 | }); 239 | 240 | it('Custom property: @root', () => { 241 | const expected = [json.store.book[2]]; 242 | const result = jsonpath({json, path: '$..book[?(@.price === @root.store.book[2].price)]'}); 243 | assert.deepEqual(result, expected); 244 | }); 245 | 246 | it('@number()', () => { 247 | const expected = [8.95, 12.99, 8.99, 22.99]; 248 | const result = jsonpath({json, path: '$.store.book..*@number()', flatten: true}); 249 | assert.deepEqual(result, expected); 250 | }); 251 | 252 | it('Regex on value', () => { 253 | const expected = [json.store.book[1].category, json.store.book[2].category, json.store.book[3].category]; 254 | const result = jsonpath({ 255 | json, 256 | path: '$..book.*[?(@property === "category" && @.match(/TION$/i))]' 257 | }); 258 | assert.deepEqual(result, expected); 259 | }); 260 | 261 | it('Regex on property', () => { 262 | const books = json.store.book; 263 | const expected = [books[2], books[3]]; 264 | const result = jsonpath({ 265 | json, 266 | path: '$..book.*[?(@property.match(/bn$/i))]^' 267 | }); 268 | assert.deepEqual(result, expected); 269 | }); 270 | }); 271 | }); 272 | -------------------------------------------------------------------------------- /docs/ts/interfaces/JSONPathCallable.html: -------------------------------------------------------------------------------- 1 | JSONPathCallable | jsonpath-plusPreparing search index...The search index is not availablejsonpath-plusjsonpath-plusJSONPathCallableInterface JSONPathCallableJSONPathCallable<T = any>(options: JSONPathOptionsAutoStart): JSONPathClassType ParametersT = anyParametersoptions: JSONPathOptionsAutoStartReturns JSONPathClassJSONPathCallable<T = any>(options: JSONPathOptions): TType ParametersT = anyParametersoptions: JSONPathOptionsReturns TJSONPathCallable<T = any>( path: string | any[], json: string | number | boolean | object | any[], callback: JSONPathCallback, otherTypeCallback: JSONPathOtherTypeCallback,): TType ParametersT = anyParameterspath: string | any[]json: string | number | boolean | object | any[]callback: JSONPathCallbackotherTypeCallback: JSONPathOtherTypeCallbackReturns TSettingsMember VisibilityProtectedInheritedThemeOSLightDarkjsonpath-plusLoading... 2 | -------------------------------------------------------------------------------- /docs/ts/assets/icons.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | addIcons(); 3 | function addIcons() { 4 | if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); 5 | const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); 6 | svg.innerHTML = `MMNEPVFCICPMFPCPTTAAATR`; 7 | svg.style.display = "none"; 8 | if (location.protocol === "file:") updateUseElements(); 9 | } 10 | 11 | function updateUseElements() { 12 | document.querySelectorAll("use").forEach(el => { 13 | if (el.getAttribute("href").includes("#icon-")) { 14 | el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); 15 | } 16 | }); 17 | } 18 | })() --------------------------------------------------------------------------------