English | 简体中文
A lightweight and powerful JSON object manipulation library that uses Linux-style path expressions to access and modify nested JSON data, allowing quick access to parent structures of data.
npm i jsonuri
# or pnpm add jsonuri / yarn add jsonuriUsage:
// Recommended on-demand import (helps with tree-shaking)
import {
get,
set,
rm,
insert,
mv,
swap,
up,
down,
normalizeUri,
parseUri,
parent,
parents,
isCircular,
walk,
walkTopDownDFS,
walkTopDownBFS,
walkBottomUpDFS,
walkBottomUpBFS,
} from 'jsonuri'
// Or import everything
import * as jsonuri from 'jsonuri'-
Separator:
/(e.g.,menu/popup/menuitem/0/value) -
Current level:
.; parent:.. -
Escaped slash: use
\/inside key names, e.g.,a\/b/cparses to segments["a/b", "c"] -
Array shortcut segments (only parsed in
get, and the current value must be an array):@first(first element)@lastor@length-1(last element)@length-N(the N-th from the end,N≥ 0 and integer)
-
Determining simple key vs complex path: If the passed
pathis a string without/or a non-negative integer index, it is treated as “simple key/index”; otherwise it is parsed as a “complex path”.
If during path parsing the parent exceeds the root (for example too many
..), parsing will be considered invalid and return an empty result.
{
"menu": {
"id": 123,
"list": [0, 1, 2, 3, 4],
"popup": {
"menuitem": [
{ "value": "New", "onclick": "CreateNewDoc()" },
{ "value": "Open", "onclick": "OpenDoc()" },
{ "value": "Close", "onclick": "CloseDoc()" }
]
}
}
}The following examples assume the variable data is the JSON above.
Read value by path.
get(data, 'menu/id') // 123
get(data, 'menu/popup/menuitem/0/value') // "New"
get(data, 'menu/popup/menuitem/0/value/..') // { value: "New", onclick: "CreateNewDoc()" }
get(data, 'menu/popup/menuitem/@last/value') // "Close"
get([10, 11, 12], '@length-2') // 11
get(data, '/') // entire dataIf
null/undefinedis encountered along the way, return that value directly; invalid path returnsundefined.
Write value by path. Missing intermediate levels will be created as objects.
For arrays, splice will be used; writing key name length will shrink/expand array length.
set(data, 'menu/id', 789)
get(data, 'menu/id') // 789
set(data, 'menu/list/7', 999) // auto expand to index 7
get(data, 'menu/list') // [0,1,2,3,4, undefined, undefined, 999]
// directly change length (simple key)
const arr = [0, 1, 2, 3]
set(arr, 'length', 2)
arr // [0,1]Protected key names:
__proto__/prototype/constructorwill be skipped (not created/written).
Delete value/item by path (delete for objects, splice for arrays).
rm(data, 'menu/id')
get(data, 'menu/id') // undefined
rm(data, 'menu/list/1')
get(data, 'menu/list') // [0,2,3,4]Only effective for arrays: insert an element before/after the index pointed by path.
direction only supports 'before' | 'after'.
// insert 9999 before index 0
insert(data, 'menu/list/0', 9999, 'before') // [9999,0,1,2,3,4]
// insert -1 after index 2
insert(data, 'menu/list/2', -1, 'after') // [9999,0,1,2,-1,3,4]If index
< 0or> lengthwill throw “Index Out of Bounds”. Source code contains an'inside'branch but only prints TODO; not provided. Text constant contains'append'but implementation does not support'append'.
Move node. Common usage is moving array elements across/same level to target index before/after.
set(data, 'menu/list', [0, 1, 2, 3, 4])
mv(data, 'menu/list/0', 'menu/list/3') // default 'before'
get(data, 'menu/list') // [1,2,3,0,4]
set(data, 'menu/list', [0, 1, 2, 3, 4])
mv(data, 'menu/list/0', 'menu/list/3', 'before')
get(data, 'menu/list') // [1,2,0,3,4]When the parent container of toPath is an object/value other than array:
- If
get(data, toPath)is not an object/function, throw error (primitive values). - Otherwise, put the value of
fromPathunder new pathtoPath + '/' + fromPath, then deletefromPath. (This is the actual behavior in source code; note the new key name/nesting may include multiple segments offromPath.)
Swap values of two paths (non-existent sources will log error and keep original).
set(data, 'menu/list', [0, 1, 2, 3, 4])
swap(data, 'menu/list/0', 'menu/list/4')
get(data, 'menu/list') // [4,1,2,3,0]Move an array element up/down by step; out-of-bounds will be clamped to the edge.
set(data, 'menu/list', [0, 1, 2, 3, 4])
up(data, 'menu/list/3') // move up 1
get(data, 'menu/list') // [0,1,3,2,4]
down(data, 'menu/list/1', 2) // move down 2
get(data, 'menu/list') // [0,3,2,1,4]Combine and normalize path segments (handle ., .., and array/nested arguments).
normalizeUri('a', 'b') // 'a/b'
normalizeUri(['a', 'b', '../'], 'c') // 'a/c'Related helpers:
parseUri(input)→ returns segment array (with escape and..handled)parent(path)→ returns parent path ornullparents(path)→ returns all parent paths from near to far (excluding self), e.g.,a/b/c→['a/b','a']
All traversal callback signatures are the same:
(value, key, parent, { uri, stop }) => any | Promise<any>
-
walk/walkTopDownDFS(same implementation) Top-down DFS-preorder; callback is not invoked on root node, only on root’s children and below. -
walkTopDownBFSTop-down BFS-preorder; callback is invoked on root node. -
walkBottomUpDFSBottom-up DFS-postorder; callback is invoked on root node (last). -
walkBottomUpBFSCollect BFS first, then callback bottom-up; callback is invoked on root node (last).
Example (DFS-preorder):
await walk({ a: { a1: 'x' } }, (val, key, parent, { uri, stop }) => {
// callback not triggered at root {a:{a1:'x'}}
// First: val={a1:'x'}, key='a', uri='a'
// Second: val='x', key='a1', uri='a/a1'
if (uri === 'a/a1') stop() // can stop further traversal
})If input object has circular references, all four traversals will throw an error at the start. Use
isCircular(obj)to pre-check.
Detect circular references.
isCircular({}) // false
const a: any = {}
set(a, '/b/c', a)
isCircular(a) // true- Invalid path or container type mismatch (e.g., executing
insert/up/downon non-array) will throw error or silently return. insertindex out of bounds throws “the Index Out of Bounds”.set(arr, 'length', n)requiresnto be non-negative integer, otherwise throws “value: n is not a natural number”.mvto non-object target withtoPathpointing to primitive type throws error.- During writing, key names
__proto__/prototype/constructorwill be skipped.
MIT