nv-json-visitor

nv-json-visitor ===============

Usage no npm install needed!

<script type="module">
  import nvJsonVisitor from 'https://cdn.skypack.dev/nv-json-visitor';
</script>

README

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

  • ISC