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 jsonuri
Usage:
// 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/c
parses to segments["a/b", "c"]
-
Array shortcut segments (only parsed in
get
, and the current value must be an array):@first
(first element)@last
or@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
path
is 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 data
If
null/undefined
is 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
/constructor
will 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
< 0
or> length
will 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
fromPath
under 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 ornull
parents(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. -
walkTopDownBFS
Top-down BFS-preorder; callback is invoked on root node. -
walkBottomUpDFS
Bottom-up DFS-postorder; callback is invoked on root node (last). -
walkBottomUpBFS
Collect 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/down
on non-array) will throw error or silently return. insert
index out of bounds throws “the Index Out of Bounds”.set(arr, 'length', n)
requiresn
to be non-negative integer, otherwise throws “value: n is not a natural number”.mv
to non-object target withtoPath
pointing to primitive type throws error.- During writing, key names
__proto__
/prototype
/constructor
will be skipped.
MIT