Create nested array in Javascript - javascript

I'm trying to convert my data from API to my needs. Would like to create a nested array from plain array. I would like to group elements by parentId property, if parentId would not exist I would put it as a root. id value is unique. Like so (raw data):
[
{id: 1, name: 'sensor'},
{id: 2, name: 'sensor', parent: 1},
{id: 3, name: 'sensor', parent: 1},
{id: 4, name: 'sensor', parent: 3},
{id: 5, name: 'sensor'},
{id: 6, name: 'sensor', parent: 5}
]
Converted Data:
const results = [
{
id: 1,
name: "sensor",
children: [
{ id: 2, name: "sensor", parent: 1 },
{
id: 3,
name: "sensor",
parent: 1,
children: [{ id: 4, name: "sensor", parent: 3 }]
}
]
},
{ id: 5, name: "sensor", children: [{ id: 6, name: "sensor", parent: 5 }] }
];
I found this recursive method but it assumes that the parent property exist for every element in an array. In my example root level element would not have parent property.
function getNestedChildren(arr, parent) {
var out = []
for(var i in arr) {
if(arr[i].parent == parent) {
var children = getNestedChildren(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}

You could take an approach which uses both relations, one from children to parent and vice versa. At the end take the children of the root node.
This approach works for unsorted data.
var data = [{ id: 1, name: 'sensor' }, { id: 2, name: 'sensor', parent: 1 }, { id: 3, name: 'sensor', parent: 1 }, { id: 4, name: 'sensor', parent: 3 }, { id: 5, name: 'sensor' }, { id: 6, name: 'sensor', parent: 5 }],
tree = function (data, root) {
var t = {};
data.forEach(o => {
Object.assign(t[o.id] = t[o.id] || {}, o);
t[o.parent] = t[o.parent] || {};
t[o.parent].children = t[o.parent].children || [];
t[o.parent].children.push(t[o.id]);
});
return t[root].children;
}(data, undefined);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Given the limited amount of information (will update if more info is added).
The algorithm would be, given an array of data entries, check if entry has a parent and if that parent exists, in which case we want to add the entry to the array of children of the parent entry otherwise add the entry as a parent.
var dataFromAPI = [
{id: 1, name: 'sensor'},
{id: 2, name: 'sensor', parent: 1},
{id: 3, name: 'sensor', parent: 1},
{id: 4, name: 'sensor', parent: 3},
{id: 5, name: 'sensor'},
{id: 6, name: 'sensor', parent: 5}
];
var transformedData = { };
dataFromAPI.forEach(function(entry){
if(entry.parent !== undefined && entry.parent in transformedData) {
transformedData[entry.parent].children.push(entry);
} else {
entry["children"] = [];
transformedData[entry.id] = entry;
}
});
console.log(transformedData);
Please note:
there are a couple assumptions made within this algorithm/code. It assumes that all parent entries exist before their child entry. It also only accounts for two levels (parent or child), meaning a child cannot act as the parent (otherwise you'd have to store the children as an object and not an array)

use a for loop to go through each item.
check if parent property exists (or has value).
If not its a child item. Attach it to appropriate parent.
to check if property exists:
var myProp = 'prop';
if (myObj.hasOwnProperty(myProp)) {
alert("yes, i have that property");
}

try
let h={}, r=[]; // result in r
d.forEach(x=> (h[x.id]=x, x.children=[]) );
d.forEach(x=> x.parent ? h[x.parent].children.push(x) : r.push(x) );
let d = [
{id: 1, name: 'sensor'},
{id: 2, name: 'sensor', parent: 1},
{id: 3, name: 'sensor', parent: 1},
{id: 4, name: 'sensor', parent: 3},
{id: 5, name: 'sensor'},
{id: 6, name: 'sensor', parent: 5}
];
let h = {},r = []; // result in r
d.forEach(x => (h[x.id] = x, x.children = []));
d.forEach(x => x.parent ? h[x.parent].children.push(x) : r.push(x));
console.log(r);

If you want that parent element should not have a parent , than you can manually check and remove fields of an object in an array that has null parent. than you can make a tree... here is an example...
const arr2 = [
{id: 1, name: 'gender', parent: null, parent_id: null },
{id: 2, name: 'material', parent: null, parent_id: null },
{id: 3, name: 'male', parent: 1, parent_name: "gender" },
{ id: 5, name: 'female', parent: 1, parent_name: "gender" },
{ id: 4, name: 'shoe', parent: 3, parent_id: "male"},
]
let newarr=[];
for(let i=0 ; i< arr2.length; i++ ){
if(arr2[i].id){
if(newarr[i] != {} ){
newarr[i] = {}
}
newarr[i].id = arr2[i].id
}
if( arr2[i].name ){
newarr[i].name = arr2[i].name
}
if( arr2[i].parent ){
newarr[i].parent = arr2[i].parent
}
if( arr2[i].parent_id ){
newarr[i].parent_id = arr2[i].parent_id
}
}
console.log('newarr', newarr );
let tree = function (data, root) {
var obj = {};
data.forEach(i => {
Object.assign(obj[i.id] = obj[i.id] || {}, i);
obj[i.parent] = obj[i.parent] || {};
obj[i.parent].children = obj[i.parent].children || [];
obj[i.parent].children.push(obj[i.id]);
});
return obj[root].children;
}(newarr, undefined);
console.log('tree ', tree);

Related

How to access last object inside nested array of objects and update its property without passing id or any parameters

I want to access last object inside nested array of objects and change its property value, I don't know its id or any any other property value
const arr = [ {id: 1, comment:'parent 01', parentId:null, reply:true, children:[{id: 11, comment:'child', reply:true, parentId:1, children:[{id: 21, comment:'super child ', reply:true,parentId:11 }] }] }, {id: 2, comment:'parent 02', reply:true, parentId:null } ]
I want to access this below object and change its property value:
{id: 21, comment:'super child ', reply:true, parentId:11 }
// result should be:
{id: 21, comment:'super child ', reply:false, parentId:11 }
Need to do it recursively and when the depth is greater than 0 and it doesn't have any children then need to modify the object(s).
const arr = [{
id: 1,
comment: 'parent 01',
parentId: null,
reply: true,
children: [{
id: 11,
comment: 'child',
reply: true,
parentId: 1,
children: [{
id: 21,
comment: 'super child ',
reply: true,
parentId: 11
}]
}]
}, {
id: 2,
comment: 'parent 02',
reply: true,
parentId: null
}];
const build = (arr, depth) => {
const nodes = [];
arr.forEach((val) => {
if (depth > 0 && !val["children"]) { //Base case
nodes.push({ ...val,
reply: false
});
return;
}
nodes.push(val["children"] ? { ...val,
"children": build(val["children"], depth + 1)
} : val);
});
return nodes;
}
console.log(build(arr, 0));

Find maximum id value in a deeply nested array of objects

I have an tree data structure with each object containing children:
const data = {
id: 1,
name: "John",
parent_id: null,
children: [{
id: 2,
name: "Tess",
parent_id: 1,
children: []
},
{
id: 3,
name: "Tom",
parent_id: 1,
children: [{
id: 4,
name: "Harry",
parent_id: 3,
children: [{
id: 7,
name: "Thabo",
parent_id: 4,
children: []
}]
},
{
id: 5,
name: "Mary",
parent_id: 3,
children: []
},
{
id: 6,
name: "Madge",
parent_id: 3,
children: []
}
]
}
]
}
Before I can add a new object to the tree, I need to determine the highest id value currently used, so I can assign the next available number as id for the new user.
To do this I created a new variable with an initial value of 0. Then I iterate over each object in the tree, and if the object's id is higher than the new id, I assign the new id the current id's value (the idea being taking the final value and adding 1 to get the new id).
let newUserID = 0;
const newID = ( root, idKey ) => {
if ( root.id > idKey ) {
idKey = root.id;
}
root.children.forEach( ( obj ) => {
newID( obj, idKey );
});
return idKey;
}
newUserID = newID( data, newUserID );
console.log( newUserID );
I expected this to return the highest id in the tree as the final value, but what actually happens is that, while the new id does increase until it matches the maximum value, it then starts decreasing again, ending on 1.
This can be seen in this JSFiddle which includes some logging to show the value of the new ID at different points in the function.
I've since solved the issue using a different approach (extracting the id values to a new array, and using Math.max() to find the highest value), but I'd like to understand why my initial approach didn't work as expected. I can see the idKey value is being updated, but then the previous value gets passed back on the recursive call, but I don't know why that's happening or how to prevent it.
First, as to why your code is broken: You just missed an assignment. Where you have
newID( obj, idKey );
you are ignoring the resulting value. You need to assign it back to idKey:
idKey = newID( obj, idKey );
That will solve your problem. We should also note that the variable name newUserID is a bit of a misnomer, since it's not the the new one you will use but the highest one found. Perhaps highestUserID would be less confusing?
However, we should point out that this can be written much more simply, using Math .max to do the heavy lifting and a dollop of recursion. Here's how I might write this:
const maxId = ({id, children = []}) =>
Math .max (id, ... children .map (maxId))
const data = {id: 1, name: "John", parent_id: null, children: [{id: 2, name: "Tess", parent_id: 1, children: []}, {id: 3, name: "Tom", parent_id: 1, children: [{id: 4, name: "Harry", parent_id: 3, children: [{id: 7, name: "Thabo", parent_id: 4, children: []}]}, {id: 5, name: "Mary", parent_id: 3, children: []}, {id: 6, name: "Madge", parent_id: 3, children: []}]}]}
console .log (maxId (data))
Simply assign the returned value of the recursive call to idKey :
let newUserID = 0;
const newID = ( root, idKey ) => {
if ( root.id > idKey ) {
idKey = root.id;
}
root.children.forEach( ( obj ) => {
idKey = newID( obj, idKey ); // <--------
});
return idKey;
}
newUserID = newID( data, newUserID );
console.log( newUserID );
Without this assignment, no matter how much you recurse, the value returned will depend only on the result of the if statement at the top. This explains the logs you were getting.
You can use recursion to solve this. Like below
const data = {
id: 1,
name: "John",
parent_id: null,
children: [
{
id: 2,
name: "Tess",
parent_id: 1,
children: [],
},
{
id: 3,
name: "Tom",
parent_id: 1,
children: [
{
id: 4,
name: "Harry",
parent_id: 3,
children: [
{
id: 7,
name: "Thabo",
parent_id: 4,
children: [],
},
],
},
{
id: 5,
name: "Mary",
parent_id: 3,
children: [],
},
{
id: 6,
name: "Madge",
parent_id: 3,
children: [],
},
],
},
],
};
const findMax = (value) => {
let max = -Infinity;
const _findMax = (data) => {
if (max < data.id) max = data.id;
data.children.forEach(_findMax);
};
_findMax(value);
return max;
};
console.log(findMax(data));
You can do:
const data = {id: 1,name: 'John',parent_id: null,children: [{ id: 2, name: 'Tess', parent_id: 1, children: [] },{id: 3,name: 'Tom',parent_id: 1,children: [{id: 4,name: 'Harry',parent_id: 3,children: [{ id: 7, name: 'Thabo', parent_id: 4, children: [] }],},{ id: 5, name: 'Mary', parent_id: 3, children: [] },{ id: 6, name: 'Madge', parent_id: 3, children: [] },],},],}
const arr = [...JSON.stringify(data).matchAll(/"id":(\d+)/g)].map(([, n]) => +n)
const result = Math.max(...arr)
console.log(result)

How to make json with infinite parent child category from database

I have an SQLite database table
+---------------------------------------------------+
| id | Cat_Name | Parent_ID |
|---------------------------------------------------+
| 1 | Asset | NULL |
+---------------------------------------------------+
| 2 | Bank | 1 |
+---------------------------------------------------+
| 3 | Cash | 1 |
+---------------------------------------------------+
| 4 | Petty Cash | 3 |
+---------------------------------------------------+
| 5 | ABC Bank | 2 |
+---------------------------------------------------+
| 6 | Dollar Account | 2 |
+---------------------------------------------------+
i can fetch the data as below
[{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2}]
In this table, category and subcategory created by the user, we can't assume how many parent and child categories will be in the table
Now I want pass the data as a nested javascript object to the front end
example
{Asset: {Bank: {ABC Bank: 5}, {Dollar Account: 6}
},
{Cash:{PettyCash: 4}, if any...}
}
Could anybody can help to get this result in the best way...
Thanks in advance
I suggest you change the design of the output object. I think the array approach would be better for the frontend.
const rawData = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar Account", Parent_ID: 2},
{ id: 7, Category_Name: "Another Wallet", Parent_ID: 4},
];
const getParentDeep = (arr, targetId) => arr.find(({ id }) => id === targetId)
?? arr.flatMap(({ children }) => getParentDeep(children, targetId))
.filter(e => e)
.at(0);
const result = rawData
.sort(({ Parent_ID: a }, { Parent_ID: b }) => a - b)
.reduce((acc, { id, Category_Name, Parent_ID }) => {
const obj = { id, name: Category_Name, children: [] };
const parentObj = getParentDeep(acc, Parent_ID);
if (parentObj) parentObj.children.push(obj)
else acc.push(obj);
return acc;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }
The result will look like this:
[{
id: 1,
name: "Asset",
children: [{
id: 2,
name: "Bank",
children: [{
id: 5,
name: "ABC Bank",
children: []
}, {
id: 6,
name: "Dollar Account",
children: []
}]
}, {
id: 3,
name: "Cash",
children: [{
id: 4,
name: "Petty Cash",
children: [{
id: 7,
name: "Another Wallet",
children: []
}]
}]
}]
}]
Presented below is one possible way to achieve the desired objective. Admittedly, it is not very elegant (& possibly not the most-efficient).
Code Snippet
// helper method to recursively-add to object
const recurAdd = (arr, idx, res) => {
// when "idx" exceeds length of array "arr",
// simply return existing result "res" object
if (idx >= arr.length) return res;
// de-structure to access parent-id & id for current elt
const { Parent_ID, id } = arr[idx];
if (Parent_ID in res) {
// parent-id exists at current object,
// so, add "id" to same object (mutate)
res[Parent_ID][id] = {};
// make recursive call for "next" elt in "arr"
return recurAdd(arr, idx+1, res);
} else {
// find next-level object where current elt will fit
const foundIt = Object.values(res).map(obj => recurAdd(arr, idx, obj));
// NOTE: "obj" is part of "res" and it gets mutated
// if found, make recursive call
if (foundIt.some(x => x !== false)) return recurAdd(arr, idx+1, res);
};
// in case parent-id is not found, simply return false
return false;
};
// helper method to substitute "id" with "category names"
const recurNamify = (obj, myMap) => (
// reconstruct object from key-value pairs of intermediate result
Object.fromEntries(
// generate intermediate result of key-value pairs
Object.entries(obj)
.map(([k, v]) => (
// substitute key (ie, "id") with category-name
Object.keys(v).length === 0
? [myMap[k], k]
: [myMap[k], recurNamify(v, myMap)]
))
// when "v" is not an empty object, make recursive call
)
);
// transform the array into nested object
const myTransform = arr => {
// first transform "Number" to "string" for id and parent-id
// because JS-object keys are string type
const myArr = arr.map(ob => ({
...ob,
id: ob.id.toString(),
Parent_ID: ob.Parent_ID.toString()
}));
// generate a dictionary/map for "id" to category-name
const myMap = myArr.reduce(
(acc, itm) => {
acc[itm.id] = itm.Category_Name
return acc;
},
{}
);
// find the index of root (ie, parent id is zero)
const rIdx = myArr.findIndex(({ Parent_ID }) => Parent_ID === '0');
// obtain the root & mutate "arr" by removing the root
const [root] = myArr.splice(rIdx, 1);
// use the helper methods to transform
return recurNamify(recurAdd(myArr, 0, {[root.id]: {}}), myMap);
};
const rawData = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2}
];
console.log('transformed: ', myTransform(rawData));
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added to the snippet above.
PS: If you'd like to add value to stackoverflow community,
Please consider reading: What to do when my question is answered
Thank you !
Here's another linked list variation, but with bi-directional object references and JSON de-/serialization in acknowledgement of the client/server relationship:
The Stack Overflow code snippet virtual console doesn't show interactive object relationships like your browser's JS console, so copy and paste this into your JS console to see the relational references in the final linked list value.
/** Conceptually similar to CSV when stringified, but preserves JSON types */
function compact (keysOrMappedKeys, array) {
const inputKeys = [];
let outputKeys = [];
const keysAreMapped = Array.isArray(keysOrMappedKeys[0]);
if (keysAreMapped) {
for (const [keyIn, keyOut] of keysOrMappedKeys) {
inputKeys.push(keyIn);
outputKeys.push(keyOut);
}
}
else {
for (const key of keysOrMappedKeys) inputKeys.push(key);
outputKeys = inputKeys;
}
const rows = [];
for (const obj of array) {
const row = [];
for (const key of inputKeys) row.push(obj[key]);
rows.push(row);
}
return [outputKeys, rows];
}
// Not actually needed for this answer:
/** The reverse of the `compact` function */
function expand ([keys, rows]) {
return rows.map(array => {
const obj = {};
for (const [index, key] of keys.entries()) obj[key] = array[index];
return obj;
});
}
/** Expects keys in the order `[ownId, parentId, ...others]` */
function createLinkedObjectList ([keys, rows]) {
const map = new Map(rows.map(row => {
const obj = {};
const iter = keys.entries();
const [ownIdIndex] = iter.next().value;
const ownId = row[ownIdIndex];
const [parentIdIndex] = iter.next().value;
const parentId = row[parentIdIndex];
for (const [index, key] of iter) obj[key] = row[index];
return [ownId, {id: ownId, parentId, value: obj}];
}));
for (const obj of map.values()) {
const parent = map.get(obj.parentId);
if (typeof parent !== 'undefined') {
obj.parent = parent;
(parent.children ??= []).push(obj);
}
delete obj.parentId;
}
return [...map.values()];
}
// Use: On the server:
// From the SQLite db:
const input = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2},
];
// Optionally, rename the keys when compacting the data structure:
const mappedKeys = [
['id', 'id'], // The ID key needs to be first
['Parent_ID', 'parent'], // The parent ID key needs to be second
// The order of the remaining keys is simply preference:
['Category_Name', 'name'],
];
const compacted = compact(mappedKeys, input);
/*
Or, just use the original key names:
const keys = [
'id', // The ID key needs to be first
'Category_Name', // The parent ID key needs to be second
// The order of the remaining keys is simply preference:
'Parent_ID',
];
const compacted = compact(keys, input);
*/
// You can send this JSON string to the client
const json = JSON.stringify(compacted);
console.log(json); // [["id","parent","name"],[[1,0,"Asset"],[2,1,"Bank"],[3,1,"Cash"],[4,3,"Petty_Cash"],[5,2,"ABC_Bank"],[6,2,"Dollar_Account"]]]
// Use: On the client:
/* After receiving the json from the server:
const json = await getDataFromServer();
Expand it into a linked list with bi-directional references
between actual parent and children objects.
This is where the order of the keys matters: */
const list = createLinkedObjectList(compacted);
console.log(list); /* Looks like this:
[
{
id: 1,
value: { name: 'Asset' },
children: [
{ id: 2, ... },
{ id: 3, ... },
],
},
{
id: 2,
value: { name: 'Bank' },
parent: { id: 1, ... },
children: [
{ id: 5, ... },
{ id: 6, ... },
],
},
{
id: 3,
value: { name: 'Cash' },
parent: { id: 1, ... },
children: [
{ id: 4, ... },
],
},
{
id: 4,
value: { name: 'Petty_Cash' },
parent: { id: 3, ... },
},
{
id: 5,
value: { name: 'ABC_Bank' },
parent: { id: 2, ... },
},
{
id: 6,
value: { name: 'Dollar_Account' },
parent: { id: 2, ... },
},
]
*/

Remove matched object from deeply nested array of objects

I have a data tree structure with children:
{ id: 1,
name: "Dog",
parent_id: null,
children: [
{
id: 2,
name: "Food",
parent_id: 1,
children: []
},
{
id: 3,
name: "Water",
parent_id: 1,
children: [
{
id: 4,
name: "Bowl",
parent_id: 3,
children: []
},
{
id: 5,
name: "Oxygen",
parent_id: 3,
children: []
},
{
id: 6,
name: "Hydrogen",
parent_id: 3,
children: []
}
]
}
]
}
This represents a DOM structure that a user could select an item from to delete by clicking the corresponding button in the DOM.
I have a known text title of the selected item for deletion from the DOM set as the variable clickedTitle. I am having trouble finding an algorithm that will allow me to delete the correct object data from the deeply nested tree.
Here is my code:
function askUserForDeleteConfirmation(e) {
const okToDelete = confirm( 'Are you sure you want to delete the item and all of its sub items?' );
if(!okToDelete) {
return;
}
const tree = getTree(); // returns the above data structure
const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
const updatedTree = removeFromTree(tree, tree, clickedTitle);
return updatedTree;
}
function removeFromTree(curNode, newTree, clickedTitle) {
if(curNode.name === clickedTitle) {
// this correctly finds the matched data item to delete but the next lines don't properly delete it... what to do?
const index = curNode.children.findIndex(child => child.name === clickedTitle);
newTree = curNode.children.slice(index, index + 1);
// TODO - what to do here?
}
for(const node of curNode.children) {
removeFromTree(node, newTree, clickedTitle);
}
return newTree;
}
I have tried to use the info from Removing matched object from array of objects using javascript without success.
If you don't mind modifying the parameter tree in-place, this should do the job. Note that it'll return null if you attempt to remove the root.
const tree = { id: 1, name: "Dog", parent_id: null, children: [ { id: 2, name: "Food", parent_id: 1, children: [] }, { id: 3, name: "Water", parent_id: 1, children: [ { id: 4, name: "Bowl", parent_id: 3, children: [] }, { id: 5, name: "Oxygen", parent_id: 3, children: [] }, { id: 6, name: "Hydrogen", parent_id: 3, children: [] } ] } ] };
const removeFromTree = (root, nameToDelete, parent, idx) => {
if (root.name === nameToDelete) {
if (parent) {
parent.children.splice(idx, 1);
}
else return null;
}
for (const [i, e] of root.children.entries()) {
removeFromTree(e, nameToDelete, root, i);
}
return tree;
};
console.log(removeFromTree(tree, "Oxygen"));
Your current code is very much on the right track. However:
newTree = curNode.children.slice(index, index + 1);
highlights a few issues: we need to manipulate the parent's children array to remove curNode instead of curNode's own children array. I pass parent objects and the child index recursively through the calls, saving the trouble of the linear operation findIndex.
Additionally, slicing from index to index + 1 only extracts one element and doesn't modify curNode.children. It's not obvious how to go about using newArray or returning it through the call stack. splice seems like a more appropriate tool for the task at hand: extracting one element in-place.
Note that this function will delete multiple entries matching nameToDelete.
I like #VictorNascimento's answer, but by applying map then filter, each children list would be iterated twice. Here is an alternative with reduce to avoid that:
function removeFromTree(node, name) {
return node.name == name
? undefined
: {
...node,
children: node.children.reduce(
(children, child) => children.concat(removeFromTree (child, name) || []), [])
}
}
In the case you want a way to remove the items in-place, as #ggorlen proposed, I'd recommend the following solution, that is simpler in my opinion:
function removeFromTree(node, name) {
if (node.name == name) {
node = undefined
} else {
node.children.forEach((child, id) => {
if (!removeFromTree(child, name)) node.children.splice(id, 1)
})
}
return node
}
I've built the algorithm as follows:
function omitNodeWithName(tree, name) {
if (tree.name === name) return undefined;
const children = tree.children.map(child => omitNodeWithName(child, name))
.filter(node => !!node);
return {
...tree,
children
}
}
You can use it to return a new tree without the item:
noHydrogen = omitNodeWithName(tree, "Hydrogen")
If it's ok to use Lodash+Deepdash, then:
let cleaned = _.filterDeep([tree],(item)=>item.name!='Hydrogen',{tree:true});
Here is a Codepen
We use object-scan for many data processing tasks. It's powerful once you wrap your head around it. Here is how you could answer your question
// const objectScan = require('object-scan');
const prune = (name, input) => objectScan(['**[*]'], {
rtn: 'bool',
abort: true,
filterFn: ({ value, parent, property }) => {
if (value.name === name) {
parent.splice(property, 1);
return true;
}
return false;
}
})(input);
const obj = { id: 1, name: 'Dog', parent_id: null, children: [{ id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [{ id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] }] }] };
console.log(prune('Oxygen', obj)); // return true iff pruned
// => true
console.log(obj);
// => { id: 1, name: 'Dog', parent_id: null, children: [ { id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [ { id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] } ] } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

Building tree array of objects from flat array of objects [duplicate]

This question already has answers here:
Build tree array from flat array in javascript
(34 answers)
Closed 2 years ago.
I want to build a tree array from flat array:
Here is the flat array:
nodes = [
{id: 1, pid: 0, name: "kpittu"},
{id: 2, pid: 0, name: "news"},
{id: 3, pid: 0, name: "menu"},
{id: 4, pid: 3, name: "node"},
{id: 5, pid: 4, name: "subnode"},
{id: 6, pid: 1, name: "cace"}
];
NB: id = node id; pid = parent node id.
I want to transform it into this array:
nodes = [{
id: 1,
name: 'kpittu',
childs: [{
id: 6,
name: 'cace'
}]
}, {
id: 2,
name: 'news'
}, {
id: 3,
name: 'menu',
childs: [{
id: 4,
name: 'node',
childs: [{
id: 5,
name: 'subnode'
}]
}]
}];
I tried to use a recursive function to achieve the expected result, but I'm looking for a better approach. Thanks for your response.
You could use a hash table and take id and pid in every loop as connected nodes.
This proposal works with unsorted data as well.
var nodes = [{ id: 6, pid: 1, name: "cace" }, { id: 1, pid: 0, name: "kpittu" }, { id: 2, pid: 0, name: "news" }, { id: 3, pid: 0, name: "menu" }, { id: 4, pid: 3, name: "node" }, { id: 5, pid: 4, name: "subnode" }],
tree = function (data, root) {
var r = [], o = {};
data.forEach(function (a) {
if (o[a.id] && o[a.id].children) {
a.children = o[a.id] && o[a.id].children;
}
o[a.id] = a;
if (a.pid === root) {
r.push(a);
} else {
o[a.pid] = o[a.pid] || {};
o[a.pid].children = o[a.pid].children || [];
o[a.pid].children.push(a);
}
});
return r;
}(nodes, 0);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can also use Map object, introduced in ES6.
let nodes = [
{ id: 1, pid: 0, name: "kpittu" },
{ id: 2, pid: 0, name: "news" },
{ id: 3, pid: 0, name: "menu" },
{ id: 4, pid: 3, name: "node" },
{ id: 5, pid: 4, name: "subnode" },
{ id: 6, pid: 1, name: "cace" }
];
function toTree(arr) {
let arrMap = new Map(arr.map(item => [item.id, item]));
let tree = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (item.pid) {
let parentItem = arrMap.get(item.pid);
if (parentItem) {
let { children } = parentItem;
if (children) {
parentItem.children.push(item);
} else {
parentItem.children = [item];
}
}
} else {
tree.push(item);
}
}
return tree;
}
let tree = toTree(nodes);
console.log(tree);
Iterate with Array#reduce and a helper object:
var nodes = [
{id: 1, pid: 0, name: "kpittu"},
{id: 2, pid: 0, name: "news"},
{id: 3, pid: 0, name: "menu"},
{id: 4, pid: 3, name: "node"},
{id: 5, pid: 4, name: "subnode"},
{id: 6, pid: 1, name: "cace"}
];
const helper = nodes.reduce((h, o) => (h[o.id] = Object.assign({}, o), h), Object.create(null));
const tree = nodes.reduce((t, node) => {
const current = helper[node.id];
if(current.pid === 0) { // if it doesn't have a parent push to root
t.push(current);
} else {
helper[node.pid].children || (helper[node.pid].children = []) // add the children array to the parent, if it doesn't exist
helper[node.pid].children.push(current); // push the current item to the parent children array
}
return t;
}, []);
console.log(tree);

Categories

Resources