├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── benchmark.js ├── jsonpointer.d.ts ├── jsonpointer.js ├── package-lock.json ├── package.json └── test.js /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [8.x, 10.x, 12.x, 14.x, 16.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v2 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: 'npm' 28 | - run: npm ci 29 | - run: npm run build --if-present 30 | - run: npm test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2015 Jan Lehnardt & Marc Bachmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON Pointer for Node.js 2 | 3 | This is an implementation of [JSON Pointer](https://tools.ietf.org/html/rfc6901). 4 | 5 | ## CLI 6 | 7 | Looking to filter JSON from the command line? Check out [jsonpointer-cli](https://github.com/joeyespo/jsonpointer-cli). 8 | 9 | ## Usage 10 | ```javascript 11 | var jsonpointer = require('jsonpointer'); 12 | var obj = { foo: 1, bar: { baz: 2}, qux: [3, 4, 5]}; 13 | 14 | jsonpointer.get(obj, '/foo'); // returns 1 15 | jsonpointer.get(obj, '/bar/baz'); // returns 2 16 | jsonpointer.get(obj, '/qux/0'); // returns 3 17 | jsonpointer.get(obj, '/qux/1'); // returns 4 18 | jsonpointer.get(obj, '/qux/2'); // returns 5 19 | jsonpointer.get(obj, '/quo'); // returns undefined 20 | 21 | jsonpointer.set(obj, '/foo', 6); // sets obj.foo = 6; 22 | jsonpointer.set(obj, '/qux/-', 6) // sets obj.qux = [3, 4, 5, 6] 23 | 24 | var pointer = jsonpointer.compile('/foo') 25 | pointer.get(obj) // returns 1 26 | pointer.set(obj, 1) // sets obj.foo = 1 27 | ``` 28 | 29 | ## Testing 30 | 31 | $ npm test 32 | All tests pass. 33 | $ 34 | 35 | [![Node.js CI](https://github.com/janl/node-jsonpointer/actions/workflows/node.js.yml/badge.svg)](https://github.com/janl/node-jsonpointer/actions/workflows/node.js.yml) 36 | 37 | ## Author 38 | 39 | (c) 2011-2021 Jan Lehnardt & Marc Bachmann 40 | 41 | Thanks to all contributors. 42 | 43 | ## License 44 | 45 | MIT License. 46 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | var jsonpointer = require('./') 2 | 3 | var i 4 | var obj = { 5 | a: 1, 6 | b: { 7 | c: 2 8 | }, 9 | d: { 10 | e: [{ a: 3 }, { b: 4 }, { c: 5 }] 11 | } 12 | } 13 | 14 | // Get 15 | console.time('get first level property') 16 | for (i = 0; i < 1e6; i++) { 17 | jsonpointer.get(obj, '/a') 18 | } 19 | console.timeEnd('get first level property') 20 | 21 | console.time('get second level property') 22 | for (i = 0; i < 1e6; i++) { 23 | jsonpointer.get(obj, '/d/e') 24 | } 25 | console.timeEnd('get second level property') 26 | 27 | console.time('get third level property') 28 | for (i = 0; i < 1e6; i++) { 29 | jsonpointer.get(obj, '/d/e/0') 30 | } 31 | console.timeEnd('get third level property') 32 | 33 | // Set 34 | console.time('set first level property') 35 | for (i = 0; i < 1e6; i++) { 36 | jsonpointer.set(obj, '/a', 'bla') 37 | } 38 | console.timeEnd('set first level property') 39 | 40 | console.time('set second level property') 41 | for (i = 0; i < 1e6; i++) { 42 | jsonpointer.set(obj, '/d/e', 'bla') 43 | } 44 | console.timeEnd('set second level property') 45 | 46 | console.time('set third level property') 47 | for (i = 0; i < 1e6; i++) { 48 | jsonpointer.set(obj, '/d/e/0', 'bla') 49 | } 50 | console.timeEnd('set third level property') 51 | 52 | console.time('push property into array') 53 | for (i = 0; i < 1e6; i++) { 54 | jsonpointer.set(obj, '/d/e/-', 'bla') 55 | } 56 | console.timeEnd('push property into array') 57 | -------------------------------------------------------------------------------- /jsonpointer.d.ts: -------------------------------------------------------------------------------- 1 | interface JSONPointer { 2 | /** 3 | * Looks up a JSON pointer in an object 4 | */ 5 | get(object: Object): any; 6 | 7 | 8 | /** 9 | * Set a value for a JSON pointer on object 10 | */ 11 | set(object: Object, value: any): void; 12 | } 13 | 14 | 15 | declare namespace JSONPointer { 16 | /** 17 | * Looks up a JSON pointer in an object 18 | */ 19 | function get(object: Object, pointer: string): any; 20 | 21 | 22 | /** 23 | * Set a value for a JSON pointer on object 24 | */ 25 | function set(object: Object, pointer: string, value: any): void; 26 | 27 | 28 | /** 29 | * Builds a JSONPointer instance from a pointer value. 30 | */ 31 | function compile(pointer: string): JSONPointer; 32 | } 33 | 34 | 35 | export = JSONPointer; 36 | -------------------------------------------------------------------------------- /jsonpointer.js: -------------------------------------------------------------------------------- 1 | var hasExcape = /~/ 2 | var escapeMatcher = /~[01]/g 3 | function escapeReplacer (m) { 4 | switch (m) { 5 | case '~1': return '/' 6 | case '~0': return '~' 7 | } 8 | throw new Error('Invalid tilde escape: ' + m) 9 | } 10 | 11 | function untilde (str) { 12 | if (!hasExcape.test(str)) return str 13 | return str.replace(escapeMatcher, escapeReplacer) 14 | } 15 | 16 | function setter (obj, pointer, value) { 17 | var part 18 | var hasNextPart 19 | 20 | for (var p = 1, len = pointer.length; p < len;) { 21 | if (pointer[p] === 'constructor' || pointer[p] === 'prototype' || pointer[p] === '__proto__') return obj 22 | 23 | part = untilde(pointer[p++]) 24 | hasNextPart = len > p 25 | 26 | if (typeof obj[part] === 'undefined') { 27 | // support setting of /- 28 | if (Array.isArray(obj) && part === '-') { 29 | part = obj.length 30 | } 31 | 32 | // support nested objects/array when setting values 33 | if (hasNextPart) { 34 | if ((pointer[p] !== '' && pointer[p] < Infinity) || pointer[p] === '-') obj[part] = [] 35 | else obj[part] = {} 36 | } 37 | } 38 | 39 | if (!hasNextPart) break 40 | obj = obj[part] 41 | } 42 | 43 | var oldValue = obj[part] 44 | if (value === undefined) delete obj[part] 45 | else obj[part] = value 46 | return oldValue 47 | } 48 | 49 | function compilePointer (pointer) { 50 | if (typeof pointer === 'string') { 51 | pointer = pointer.split('/') 52 | if (pointer[0] === '') return pointer 53 | throw new Error('Invalid JSON pointer.') 54 | } else if (Array.isArray(pointer)) { 55 | for (const part of pointer) { 56 | if (typeof part !== 'string' && typeof part !== 'number') { 57 | throw new Error('Invalid JSON pointer. Must be of type string or number.') 58 | } 59 | } 60 | return pointer 61 | } 62 | 63 | throw new Error('Invalid JSON pointer.') 64 | } 65 | 66 | function get (obj, pointer) { 67 | if (typeof obj !== 'object') throw new Error('Invalid input object.') 68 | pointer = compilePointer(pointer) 69 | var len = pointer.length 70 | if (len === 1) return obj 71 | 72 | for (var p = 1; p < len;) { 73 | obj = obj[untilde(pointer[p++])] 74 | if (len === p) return obj 75 | if (typeof obj !== 'object' || obj === null) return undefined 76 | } 77 | } 78 | 79 | function set (obj, pointer, value) { 80 | if (typeof obj !== 'object') throw new Error('Invalid input object.') 81 | pointer = compilePointer(pointer) 82 | if (pointer.length === 0) throw new Error('Invalid JSON pointer for set.') 83 | return setter(obj, pointer, value) 84 | } 85 | 86 | function compile (pointer) { 87 | var compiled = compilePointer(pointer) 88 | return { 89 | get: function (object) { 90 | return get(object, compiled) 91 | }, 92 | set: function (object, value) { 93 | return set(object, compiled, value) 94 | } 95 | } 96 | } 97 | 98 | exports.get = get 99 | exports.set = set 100 | exports.compile = compile 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonpointer", 3 | "description": "Simple JSON Addressing.", 4 | "tags": [ 5 | "util", 6 | "simple", 7 | "util", 8 | "utility" 9 | ], 10 | "version": "5.0.0", 11 | "author": "Jan Lehnardt ", 12 | "contributors": [ 13 | "Joe Hildebrand ", 14 | "Marc Bachmann " 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/janl/node-jsonpointer.git" 19 | }, 20 | "bugs": { 21 | "url": "http://github.com/janl/node-jsonpointer/issues" 22 | }, 23 | "engines": { 24 | "node": ">=0.10.0" 25 | }, 26 | "main": "./jsonpointer", 27 | "typings": "jsonpointer.d.ts", 28 | "files": [ 29 | "jsonpointer.js", 30 | "jsonpointer.d.ts" 31 | ], 32 | "scripts": { 33 | "test": "npm run test:standard && npm run test:all", 34 | "test:standard": "standard", 35 | "test:all": "node test.js", 36 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 37 | }, 38 | "license": "MIT", 39 | "devDependencies": { 40 | "semantic-release": "^18.0.0", 41 | "standard": "^16.0.4" 42 | }, 43 | "standard": { 44 | "ignore": [ 45 | "test.js" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var jsonpointer = require('./jsonpointer') 3 | 4 | var obj = { 5 | a: 1, 6 | b: { 7 | c: 2 8 | }, 9 | d: { 10 | e: [{ a: 3 }, { b: 4 }, { c: 5 }] 11 | }, 12 | nullValue: null 13 | } 14 | 15 | assert.strictEqual(jsonpointer.get(obj, '/nullValue'), null) 16 | assert.strictEqual(jsonpointer.get(obj, '/nullValue/e'), undefined) 17 | 18 | // set returns old value 19 | assert.strictEqual(jsonpointer.set(obj, '/a', 2), 1) 20 | assert.strictEqual(jsonpointer.set(obj, '/b/c', 3), 2) 21 | assert.strictEqual(jsonpointer.set(obj, '/d/e/0/a', 4), 3) 22 | assert.strictEqual(jsonpointer.set(obj, '/d/e/1/b', 5), 4) 23 | assert.strictEqual(jsonpointer.set(obj, '/d/e/2/c', 6), 5) 24 | 25 | // set nested properties 26 | assert.strictEqual(jsonpointer.set(obj, '/f/g/h/i', 6), undefined) 27 | assert.strictEqual(jsonpointer.get(obj, '/f/g/h/i'), 6) 28 | 29 | // set an array 30 | assert.strictEqual(jsonpointer.set(obj, '/f/g/h/foo/-', 'test'), undefined) 31 | var arr = jsonpointer.get(obj, '/f/g/h/foo') 32 | assert(Array.isArray(arr), 'set /- creates an array.') 33 | assert.strictEqual(arr[0], 'test') 34 | 35 | assert.strictEqual(jsonpointer.get(obj, '/a'), 2) 36 | assert.strictEqual(jsonpointer.get(obj, '/b/c'), 3) 37 | assert.strictEqual(jsonpointer.get(obj, '/d/e/0/a'), 4) 38 | assert.strictEqual(jsonpointer.get(obj, '/d/e/1/b'), 5) 39 | assert.strictEqual(jsonpointer.get(obj, '/d/e/2/c'), 6) 40 | 41 | // can set `null` as a value 42 | assert.strictEqual(jsonpointer.set(obj, '/f/g/h/foo/0', null), 'test') 43 | assert.strictEqual(jsonpointer.get(obj, '/f/g/h/foo/0'), null) 44 | assert.strictEqual(jsonpointer.set(obj, '/b/c', null), 3) 45 | assert.strictEqual(jsonpointer.get(obj, '/b/c'), null) 46 | 47 | assert.strictEqual(jsonpointer.get(obj, ''), obj) 48 | assert.throws(function () { jsonpointer.get(obj, 'a') }, validateError) 49 | assert.throws(function () { jsonpointer.get(obj, 'a/') }, validateError) 50 | 51 | // can unset values with `undefined` 52 | jsonpointer.set(obj, '/a', undefined) 53 | assert.strictEqual(jsonpointer.get(obj, '/a'), undefined) 54 | jsonpointer.set(obj, '/d/e/1', undefined) 55 | assert.strictEqual(jsonpointer.get(obj, '/d/e/1'), undefined) 56 | 57 | // returns `undefined` when path extends beyond any existing objects 58 | assert.strictEqual(jsonpointer.get(obj, '/x/y/z'), undefined) 59 | 60 | function validateError (err) { 61 | if ((err instanceof Error) && /Invalid JSON pointer/.test(err.message)) { 62 | return true 63 | } 64 | } 65 | 66 | var complexKeys = { 67 | 'a/b': { 68 | c: 1 69 | }, 70 | d: { 71 | 'e/f': 2 72 | }, 73 | '~1': 3, 74 | '01': 4 75 | } 76 | 77 | assert.strictEqual(jsonpointer.get(complexKeys, '/a~1b/c'), 1) 78 | assert.strictEqual(jsonpointer.get(complexKeys, '/d/e~1f'), 2) 79 | assert.strictEqual(jsonpointer.get(complexKeys, '/~01'), 3) 80 | assert.strictEqual(jsonpointer.get(complexKeys, '/01'), 4) 81 | assert.strictEqual(jsonpointer.get(complexKeys, '/a/b/c'), undefined) 82 | assert.strictEqual(jsonpointer.get(complexKeys, '/~1'), undefined) 83 | 84 | // draft-ietf-appsawg-json-pointer-08 has special array rules 85 | var ary = ['zero', 'one', 'two'] 86 | assert.strictEqual(jsonpointer.get(ary, '/01'), undefined) 87 | 88 | assert.strictEqual(jsonpointer.set(ary, '/-', 'three'), undefined) 89 | assert.strictEqual(ary[3], 'three') 90 | 91 | // Examples from the draft: 92 | var example = { 93 | foo: ['bar', 'baz'], 94 | '': 0, 95 | 'a/b': 1, 96 | 'c%d': 2, 97 | 'e^f': 3, 98 | 'g|h': 4, 99 | 'i\\j': 5, 100 | 'k\'l': 6, 101 | ' ': 7, 102 | 'm~n': 8 103 | } 104 | 105 | assert.strictEqual(jsonpointer.get(example, ''), example) 106 | var ans = jsonpointer.get(example, '/foo') 107 | assert.strictEqual(ans.length, 2) 108 | assert.strictEqual(ans[0], 'bar') 109 | assert.strictEqual(ans[1], 'baz') 110 | assert.strictEqual(jsonpointer.get(example, '/foo/0'), 'bar') 111 | assert.strictEqual(jsonpointer.get(example, '/'), 0) 112 | assert.strictEqual(jsonpointer.get(example, '/a~1b'), 1) 113 | assert.strictEqual(jsonpointer.get(example, '/c%d'), 2) 114 | assert.strictEqual(jsonpointer.get(example, '/e^f'), 3) 115 | assert.strictEqual(jsonpointer.get(example, '/g|h'), 4) 116 | assert.strictEqual(jsonpointer.get(example, '/i\\j'), 5) 117 | assert.strictEqual(jsonpointer.get(example, '/k\'l'), 6) 118 | assert.strictEqual(jsonpointer.get(example, '/ '), 7) 119 | assert.strictEqual(jsonpointer.get(example, '/m~0n'), 8) 120 | 121 | // jsonpointer.compile(path) 122 | var a = { foo: 'bar', foo2: null } 123 | var pointer = jsonpointer.compile('/foo') 124 | assert.strictEqual(pointer.get(a), 'bar') 125 | assert.strictEqual(pointer.set(a, 'test'), 'bar') 126 | assert.strictEqual(pointer.get(a), 'test') 127 | assert.deepEqual(a, { foo: 'test', foo2: null }) 128 | 129 | // Read subproperty of null value 130 | var pointerNullValue = jsonpointer.compile('/foo2/baz') 131 | assert.strictEqual(pointerNullValue.get(a), undefined) 132 | 133 | var b = {} 134 | jsonpointer.set({}, '/constructor/prototype/boo', 'polluted') 135 | assert(!b.boo, 'should not boo') 136 | 137 | var c = {} 138 | jsonpointer.set({}, '/__proto__/boo', 'polluted') 139 | assert(!c.boo, 'should not boo') 140 | 141 | var d = {} 142 | jsonpointer.set({}, '/foo/__proto__/boo', 'polluted') 143 | assert(!d.boo, 'should not boo') 144 | 145 | jsonpointer.set({}, '/foo/__proto__/__proto__/boo', 'polluted') 146 | assert(!d.boo, 'should not boo') 147 | 148 | var e = {} 149 | jsonpointer.set({}, '/foo/constructor/prototype/boo', 'polluted') 150 | assert(!e.boo, 'should not boo') 151 | 152 | jsonpointer.set({}, '/foo/constructor/constructor/prototype/boo', 'polluted') 153 | assert(!e.boo, 'should not boo') 154 | 155 | assert.throws(function () { jsonpointer.set({}, [['__proto__'], 'boo'], 'polluted')}, validateError) 156 | assert.throws(function () { jsonpointer.set({}, [[['__proto__']], 'boo'], 'polluted')}, validateError) 157 | assert.throws(function () { jsonpointer.set({}, [['__proto__'], ['__proto__'], 'boo'], 'polluted')}, validateError) 158 | assert.throws(function () { jsonpointer.set({}, [[['__proto__']], [['__proto__']], 'boo'], 'polluted')}, validateError) 159 | assert.throws(function () { jsonpointer.set({}, [['__proto__'], ['__proto__'], ['__proto__'], 'boo'], 'polluted')}, validateError) 160 | assert.throws(function () { jsonpointer.set({}, [['foo'], ['__proto__'], 'boo'], 'polluted')}, validateError) 161 | assert.throws(function () { jsonpointer.set({}, [['foo'], ['__proto__'], ['__proto__'], 'boo'], 'polluted')}, validateError) 162 | assert.throws(function () { jsonpointer.set({}, [['constructor'], ['prototype'], 'boo'], 'polluted')}, validateError) 163 | assert.throws(function () { jsonpointer.set({}, [['constructor'], ['constructor'], ['prototype'], 'boo'], 'polluted')}, validateError) 164 | 165 | console.log('All tests pass.') 166 | --------------------------------------------------------------------------------