stackoverlflowAdmin
stackoverlflowAdmin

Reputation: 3

encode path of nested javascript object

I want to write a function that takes a nested json object and writes a path array in each of objects that denote the "path" or hierarchy of the object itself. the path array should be populated by the id of the parent nodes. the limits array objects should have a path and since they don't have an id, an enumarated key is given to them based on the index in that array. my attempt is below however I am not getting the correct path at certain nth levels down.

let nestedObject = {
    "name":"bob",
    "root_rule":{
       "id":0,
       "limits":[
          {
             "value":0
          }
       ],
       "rules":[
          {
             "id":15,
             "limits":[
                
             ],
             "rules":[
                
             ]
          },
          {
             "id":16,
             "limits":[
                {
                   "value":25000000
                },
                {
                   "value":50001
                },
                {
                   "value":25000001
                }
             ],
             "rules":[
                {
                   "id":20,
                   "limits":[
                      {
                         "value":0
                      }
                   ],
                   "rules":[
                      {
                         "id":21,
                         "limits":[
                            
                         ],
                         "rules":[
                            
                         ]
                      }
                   ]
                }
             ]
          }
       ]
    }
 }
function encodeNestedPath(nestedJson, pathList, type, parent = null) {

    // process rood node
    if ((typeof nestedJson) == 'object') {
        
        if(nestedJson && nestedJson.condition && nestedJson.condition.name === 'ROOT') {
            nestedJson['path'] = [String(nestedJson['id'])];
        }
        
        if(nestedJson.limits) {
            encodeNestedPath(nestedJson.limits, nestedJson['path'], 'limits', nestedJson['id']);
        }
        if(nestedJson.rules) {
            encodeNestedPath(nestedJson.rules, nestedJson['path'], 'rules', nestedJson['id']);
        }
    }

    // traverse children nodes
    if (Array.isArray(nestedJson)) {
        for (const [i, element] of nestedJson.entries()) {

            element['path'] = [];

            if(String(parent)) {
                element['path'].push(String(parent))
            }

            let parentId
            if(!element.hasOwnProperty('id')) {
                element['path'].push(type+'_'+i)
                pathList = null
            } else {
                element['path'].push(String(element['id']))
                parentId = element['id']
            }

            if(element.limits) {
                encodeNestedPath(element.limits, element['path'], 'limits', parentId);
            }
            if(element.rules) {
                encodeNestedPath(element.rules, element['path'], 'rules', parentId);
            }
        }
    }
}



encodeNestedPath(nestedObject['root_rule'], null, null, null)
console.log(JSON.stringify(nestedObject['root_rule'], null, 4))

Desiring output is like this. image of desired output

Upvotes: 0

Views: 324

Answers (2)

vincent
vincent

Reputation: 2181

Not a big fan of reinventing the wheel. I'd recommend you use a library for the basics. We use object-scan for all our data processing these days. It's powerful once you wrap your head around how to use it. Here is how you'd use it to solve your questions:

.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/[email protected]/lib/index.min.js';

const process = (data) => objectScan(['**.id', '**.limits[*]'], {
  rtn: 'bool',
  filterFn: ({ value, parents, property }) => {
    const path = parents.filter((p) => 'id' in p).map(({ id }) => id).reverse();
    if (property === 'id') {
      parents[0].path = path;
    } else {
      value.path = path.concat(`limit_${property}`);
    }
  }
})(data);

const data = { name: 'bob', root_rule: { id: 0, limits: [{ value: 0 }], rules: [{ id: 15, limits: [], rules: [] }, { id: 16, limits: [{ value: 25000000 }, { value: 50001 }, { value: 25000001 }], rules: [{ id: 20, limits: [{ value: 0 }], rules: [{ id: 21, limits: [], rules: [] }] }] }] } };

console.log(process(data)); // returns true iff match
// => true

console.log(data);
// => { name: 'bob', root_rule: { id: 0, limits: [ { value: 0, path: [ 0, 'limit_0' ] } ], rules: [ { id: 15, limits: [], rules: [], path: [ 0, 15 ] }, { id: 16, limits: [ { value: 25000000, path: [ 0, 16, 'limit_0' ] }, { value: 50001, path: [ 0, 16, 'limit_1' ] }, { value: 25000001, path: [ 0, 16, 'limit_2' ] } ], rules: [ { id: 20, limits: [ { value: 0, path: [ 0, 16, 20, 'limit_0' ] } ], rules: [ { id: 21, limits: [], rules: [], path: [ 0, 16, 20, 21 ] } ], path: [ 0, 16, 20 ] } ], path: [ 0, 16 ] } ], path: [ 0 ] } }
</script>

Disclaimer: I'm the author of object-scan

Alternative way that gives you more control

.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/[email protected]/lib/index.min.js';

const process = (data) => {
  const extractPath = (parents) => parents
    .filter((p) => 'id' in p)
    .map(({ id }) => id)
    .reverse();
  const logic = {
    '**.id': ({ parents }) => {
      parents[0].path = extractPath(parents);
    },
    '**.limits[*]': ({ value, parents, property }) => {
      value.path = [...extractPath(parents), `limit_${property}`];
    }
  };
  return objectScan(Object.keys(logic), {
    rtn: 'bool',
    filterFn: (kwargs) => kwargs.matchedBy.forEach((n) => logic[n](kwargs))
  })(data);
};

const data = { name: 'bob', root_rule: { id: 0, limits: [{ value: 0 }], rules: [{ id: 15, limits: [], rules: [] }, { id: 16, limits: [{ value: 25000000 }, { value: 50001 }, { value: 25000001 }], rules: [{ id: 20, limits: [{ value: 0 }], rules: [{ id: 21, limits: [], rules: [] }] }] }] } };

console.log(process(data));
// => true

console.log(data);
// => { name: 'bob', root_rule: { id: 0, limits: [ { value: 0, path: [ 0, 'limit_0' ] } ], rules: [ { id: 15, limits: [], rules: [], path: [ 0, 15 ] }, { id: 16, limits: [ { value: 25000000, path: [ 0, 16, 'limit_0' ] }, { value: 50001, path: [ 0, 16, 'limit_1' ] }, { value: 25000001, path: [ 0, 16, 'limit_2' ] } ], rules: [ { id: 20, limits: [ { value: 0, path: [ 0, 16, 20, 'limit_0' ] } ], rules: [ { id: 21, limits: [], rules: [], path: [ 0, 16, 20, 21 ] } ], path: [ 0, 16, 20 ] } ], path: [ 0, 16 ] } ], path: [ 0 ] } }
</script>

Disclaimer: I'm the author of object-scan

Upvotes: 0

trincot
trincot

Reputation: 351328

One of the issues in your function is that you don't do anything useful with the pathList argument. Instead, you start building a path from scratch with:

nestedJson['path'] = [String(nestedJson['id'])];

and with:

element['path'] = [];

where the latter is followed by a single push.

In essence all your path properties are set to arrays of length 1.

Here is an implementation that iterates through the object hierarchy (without special treatment for hardcoded properties like limits or rules) and applies the logic you described:

function encodeNestedPath(obj, pathList=[], parentKey) {
    if (Array.isArray(obj)) {
         // remove plural and append underscore
        parentKey = parentKey !== undefined ? parentKey.replace(/s?$/, "_") : "";
        obj.forEach((elem, i) => encodeNestedPath(elem, pathList, parentKey + i));
    } else if (Object(obj) === obj) {
        if ("id" in obj) parentKey = obj.id;
        if (parentKey !== undefined) pathList = pathList.concat(parentKey);
        if (pathList.length) obj.path = pathList
        for (let prop in obj) encodeNestedPath(obj[prop], pathList, prop);
    }
}

// object from the question:
let nestedObject = {"name": "bob","root_rule":{"id":0,"limits":[{"value":0}],"rules":[{"id":15,"limits":[],"rules":[]},{"id":16,"limits":[{"value":25000000},{"value":50001},{"value":25000001}],"rules":[{"id":20,"limits":[{"value":0}],"rules":[{"id":21,"limits":[],"rules":[]}]}]}]}};

encodeNestedPath(nestedObject);
console.log(nestedObject);

Upvotes: 1

Related Questions