Reputation: 3
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.
Upvotes: 0
Views: 324
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
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