nv-json-visitor
install
- npm install nv-json-visitor
usage
const jpcfg = require("nv-json-param-cfg");
const {visit,gen} = require("nv-json-visitor");
////
visit(J,enter_handler,leave_handler,cfg=jpcfg.DFLT_CFG())
////- gen is for debug using
gen(J,enter_handler,leave_handler,cfg=jpcfg.DFLT_CFG())
enter_handler: (ctx)=> {
////mutually exclusive operators
ctx.$pass() //do nothing
ctx.$replace_with(V,K?)
ctx.$disconn() // remove self
ctx.$insert_before(V,K?)
// parent [10,20,30] , <ctx-of-20>.insert_before(111) will be [10,111,20,30]
ctx.$insert_after(V,K?)
// parent [10,20,30] , <ctx-of-20>.insert_after(111) will be [10,20,111,30]
ctx.$add_fstch(V,K?)
// only work on empty [] OR empty {}
// others SHOULD use insert_...
ctx.$rename_key(K)
// replace only key, only work when ctx.is_prop()===true ,whose parent is dict
ctx.$skip()
// stop visit descendants of this node
////other operaters
ctx.YIELD() //used in gen(....)
ctx.BREAK() //terminate visiting
}
leave_handler: (ctx) => {/*...similiar to enter_handler...*/}
example
replace_with(V,K@optional)
var J = {
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
emptya:[],
ary: [ 'a', 'b', 'c' ],
emptyd:{}
}
var enter_handler = (ctx)=>{
}
var leave_handler = (ctx)=>{
if(ctx.is_ele()) {
ctx.$replace_with(ctx.value.toUpperCase());
} else if(ctx?.key?.includes("empty")) {
ctx.$replace_with(0,ctx.key.toUpperCase())
} else {}
}
visit(J,enter_handler,leave_handler);
console.log(J)
/*
{
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
EMPTYA: 0,
ary: [ 'A', 'B', 'C' ],
EMPTYD: 0
}
*/
rename_key(K)
var J = {
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
emptya:[],
ary: [ 'a', 'b', 'c' ],
emptyd:{}
}
var enter_handler = (ctx)=>{
ctx.$rename_key(`renamed_${ctx.key}`);
}
var leave_handler = (ctx)=>{
}
visit(J,enter_handler,leave_handler);
console.log(J)
/*
{
renamed_undef: undefined,
renamed_null: null,
renamed_num: 1,
renamed_str: 's',
renamed_t: true,
renamed_f: false,
renamed_emptya: [],
renamed_ary: [ 'a', 'b', 'c' ],
renamed_emptyd: {}
}
*/
disconn();
var J = {
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
emptya:[],
ary: [ 'a', 'b', 'c' ],
emptyd:{}
}
var enter_handler = (ctx)=>{
}
var leave_handler = (ctx)=>{
if(ctx.value === 'b' || ctx.value === 's') {
ctx.$disconn();
} else {
ctx.YIELD()
}
}
visit(J,enter_handler,leave_handler);
console.log(J)
/*
{
undef: undefined,
null: null,
num: 1,
t: true,
f: false,
emptya: [],
ary: [ 'a', 'c' ],
emptyd: {}
}
*/
insert_before
var J = {
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
emptya:[],
ary: [ 'a', 'b', 'c' ],
emptyd:{}
}
//if in enter_handler , will exec recursively
var enter_handler = (ctx)=>{
}
//so do it in leave_handler
var leave_handler = (ctx)=>{
if(ctx.value === 'b') {
ctx.$insert_before('B')
} else if(ctx.key === 'emptyd'){
ctx.$insert_before({m:999,n:888},'before-emptyd')
} else {}
}
visit(J,enter_handler,leave_handler);
console.log(J)
/*
{
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
emptya: [],
ary: [ 'a', 'B', 'b', 'c' ],
'before-emptyd': { m: 999, n: 888 },
emptyd: {}
}
*/
insert_after
var J = {
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
emptya:[],
ary: [ 'a', 'b', 'c' ],
emptyd:{},
dict: {
x:777,
y:888
}
}
//this time do it in enter_handler, to observe recursive behavior
var enter_handler = (ctx)=>{
if(ctx.is_ele() && ctx.parent.length<8) {
ctx.$insert_after(ctx.value+ctx.value)
} else if(
ctx.is_prop() &&
ctx.index <3 &&
ctx.pl.includes('dict') &&
ctx.pl.length <4
) {
ctx.$insert_after({x:777,y:888},'k-'+ctx.index)
} else {
}
}
var leave_handler = (ctx)=>{
}
visit(J,enter_handler,leave_handler);
console.dir(J,{depth:null})
/*
{
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
emptya: [],
ary: [
'a',
'aa',
'aaaa',
'aaaaaaaa',
'aaaaaaaaaaaaaaaa',
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'b',
'c'
],
emptyd: {},
dict: {
x: 777,
'k-0': {
x: 777,
'k-0': { x: 777, y: 888 },
'k-1': { x: 777, y: 888 },
'k-2': { x: 777, y: 888 },
y: 888
},
'k-1': {
x: 777,
'k-0': { x: 777, y: 888 },
'k-1': { x: 777, y: 888 },
'k-2': { x: 777, y: 888 },
y: 888
},
'k-2': {
x: 777,
'k-0': { x: 777, y: 888 },
'k-1': { x: 777, y: 888 },
'k-2': { x: 777, y: 888 },
y: 888
},
y: 888
}
}
*/
skip
var J = {
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
emptya:[],
ary: [ 'a', 'b', 'c' ],
emptyd:{}
}
var enter_handler = (ctx)=>{
if(ctx.is_ary() && ctx.value.length >0) {
ctx.$skip(); //skip decedents of [a,b,c]: whiches are a,b,c
} else {
}
ctx.YIELD()
}
var leave_handler = (ctx)=>{
}
var g = gen(J,enter_handler,leave_handler);
var arr = Array.from(g);
> arr.map(r=>[r.stage,r.self.value])
[
[
'entering',
{
undef: undefined,
null: null,
num: 1,
str: 's',
t: true,
f: false,
emptya: [],
ary: [Array],
emptyd: {}
}
],
[ 'entering', undefined ],
[ 'entering', null ],
[ 'entering', 1 ],
[ 'entering', 's' ],
[ 'entering', true ],
[ 'entering', false ],
[ 'entering', [] ],
[ 'entering', [ 'a', 'b', 'c' ] ],
[ 'entering', {} ]
]
>
CFG
> require("nv-json-param-cfg").DFLT_CFG()
{
ignore_primitive_type: true,
is_forced_leaf: [Function: DFLT_IS_FORCED_LEAF],
enable_circular: false,
rtrn_container_map: false,
snapshot: false,
snap_with_parent: false,
snap_with_rj: false
}
>
CTX METHODS
is
ctx.is_ele //parent is ary
ctx.is_prop //parent is dict
////relation
ctx.is_leaf ctx.is_lonely ctx.is_root
ctx.is_fstch ctx.is_lstch ctx.is_isolated
////type
ctx.is_ary
ctx.is_dict
//work when cfg.ignore_primitive_type = false
ctx.is_undefined
ctx.is_null
ctx.is_num
ctx.is_str
ctx.is_external // a special type treated as leaf, worked when match cfg.is_forced_leaf(V)
ctx.is_ref //work when cfg.enable_circular=true
attribs
ctx.value
ctx.index
ctx.key
ctx.parent
ctx.pl
ctx.type //work when cfg.ignore_primitive_type = false
ctx.ref //work when cfg.enable_circular=true
ctx.hash //work when cfg.enable_circular=true
ctx.jdesc
value node
ctx.fstch
ctx.parent
ctx.rsib
ctx.lsib
ctx.lstch
internal node
ctx.$fstch_ ctx.$lsib_ ctx.$lstch_ ctx.$parent_
ctx.$rsib_
APIS
{
jcnst: {
DTYPES: Dtype(9) {
'undefined',
'null',
'bool',
'str',
'num',
'ary',
'dict',
'external',
'ref'
},
MUST_LEAF_DTYPES: MustLeafDtype(7) {
'undefined',
'null',
'bool',
'str',
'num',
'external',
'ref'
},
CONTAINER_DTYPES: ContainerDtype(2) { 'ary', 'dict' },
STAGES: Stage {
'0': 'entering',
'1': 'leaving',
init: -1,
'-1': 'init',
entering: 0,
leaving: 1
},
OPERATIONS: Operation {
'0': 'pass',
'1': 'replace_with',
'2': 'disconn',
'3': 'insert_before',
'4': 'insert_after',
'5': 'add_fstch',
'6': 'rename_key',
'7': 'skip',
pass: 0,
replace_with: 1,
disconn: 2,
insert_before: 3,
insert_after: 4,
add_fstch: 5,
rename_key: 6,
skip: 7
},
OPERATION_ARY: [
'pass',
'replace_with',
'disconn',
'insert_before',
'insert_after',
'add_fstch',
'rename_key',
'skip'
],
NOLEAF_ONLY_OPERATIONS: [
'disconn',
'insert_before',
'insert_after',
'add_fstch',
'rename_key',
'skip'
],
FROMS: From {
'0': 'self',
'1': 'parent',
'2': 'lsib',
'3': 'lstch',
'4': '@prev_from',
self: 0,
parent: 1,
lsib: 2,
lstch: 3,
'@prev_from': 4
},
CTRLS: [ 'YIELD', 'BREAK', 'SKIP' ],
ATTRS: Attr(6) { 'type', 'value', 'index', 'key', 'hash', 'ref' },
'$NAMES': $Name(5) { 'fstch', 'rsib', 'parent', 'lsib', 'lstch' },
'$NAME_TO_ABBR_DICT': { fstch: 'fc', rsib: 'rb', parent: 'pr', lsib: 'lb', lstch: 'lc' }
},
jpcfg: {
DFLT_HANDLER: [Function: DFLT_HANDLER],
DFLT_IS_FORCED_LEAF: [Function: DFLT_IS_FORCED_LEAF],
DFLT_CFG: [Function: DFLT_CFG],
YIELD_PARAM_NAMES: [ 'snapshot', 'snap_with_parent', 'snap_with_rj' ],
YIELD_PARAM_DFLTS: [ false, false, false ],
OPERATION_PARAM_DESCS: {
pass: [],
replace_with: [Array],
disconn: [],
insert_before: [Array],
insert_after: [Array],
add_fstch: [Array],
rename_key: [Array],
skip: []
},
_DFLT_FLTR: [Function: _DFLT_FLTR],
DFLT_FLTR: [Function: DFLT_FLTR]
},
visit: [Function: visit],
gen: [GeneratorFunction: gen],
fltr: {
vfltr: [Function: vfltr],
entries: [Function: entries],
dicts: [Function: dicts],
arys: [Function: arys],
types: [Function: types],
strs: [Function: strs],
nums: [Function: nums],
bools: [Function: bools]
},
api: {
max_depth: [Function: max_depth],
group_by_depth: [Function: group_by_depth],
size: [Function: size]
},
b2u: { gen: [GeneratorFunction: gen] } //bottom-to-up layers
}
PERFORMANCE
- for support rename_key, the performance is NOT good for big-json(> 10M)
use a big json of 600000 nodes
TEST# node pass-visit-perf.js
avg. elapsed time: 259.10229ms over 8 runs on 5026888 nodes
TEST# node pass-visit-perf.js
avg. elapsed time: 275.093326ms over 8 runs on 5026888 nodes
TEST# node pass-visit-perf.js
avg. elapsed time: 258.368676ms over 8 runs on 5026888 nodes
TEST# node pass-visit-perf.js
avg. elapsed time: 253.991991ms over 8 runs on 5026888 nodes
TEST# node pass-visit-perf.js
avg. elapsed time: 280.049259ms over 8 runs on 5026888 nodes
LICENSE