How to get parent id in unlimited nested array in JavaScript - javascript

It is as the title, but I am facing a problem!
I want to create getParentId(array, id) function.
This function get parent id by child id.
const array = [{
id: 1,
title: 'hello',
children: [{
id: 3,
title: 'hello',
children: [{
id: 4,
title:'hello',
children: [
{ id: 5, title: 'hello'},
{ id: 6, title: 'hello'}
]
},
{
id: 7,
title: 'hello'
}]
}]
},
{
id: 2,
title: 'hello',
children: [
{ id: 8, title: 'hello'}
]
}]
This array may nest indefinitely
Expected Result:
getParentId(array, 3) -> 1
getParentId(array, 5) -> 4
getParentId(array, 6) -> 4
getParentId(array, 8) -> 2
getParentId(array, 2) -> null
I would be grateful if you would send me information.

You could take a recursive approach by iterating the actual array and their children and stop if the id is found.
function getParentId(array, id, parentId = null) {
return array.some(o => {
if (o.id === id) return true;
const temp = getParentId(o.children || [], id, o.id);
if (temp !== null) {
parentId = temp;
return true;
}
})
? parentId
: null;
}
const array = [{ id: 1, title: 'hello', children: [{ id: 3, title: 'hello', children: [{ id: 4, title:'hello', children: [{ id: 5, title: 'hello' }, { id: 6, title: 'hello' }] }, { id: 7, title: 'hello' }] }] }, { id: 2, title: 'hello', children: [{ id: 8, title: 'hello' }] }];
console.log(getParentId(array, 3)); // 1
console.log(getParentId(array, 5)); // 4
console.log(getParentId(array, 6)); // 4
console.log(getParentId(array, 8)); // 2
console.log(getParentId(array, 2)); // null
console.log(getParentId(array, 7)); // 3
console.log(getParentId(array, 4)); // 3
.as-console-wrapper { max-height: 100% !important; top: 0; }

Nina Scholz's answer is great, but here's a slightly less functional approach (not using Array.some), if you like it better:
const array = [{id: 1, title: 'hello', children: [{id: 3, title: 'hello', children: [{id: 4, title:'hello', children: [{ id: 5, title: 'hello'}, { id: 6, title: 'hello'}]}, {id: 7, title: 'hello'}]}]}, {id: 2, title: 'hello', children: [{ id: 8, title: 'hello'}]}];
function getParentId(array, id, parentId = null) {
// For every entry in the array
for (const entry of array) {
// If the ID matches, return the current parent ID
if (entry.id === id) {
return parentId;
}
// Otherwise, call the same function on its children, passing itself as the parent.
// If there was a match, return it.
if (entry.children && (deeperParentId = getParentId(entry.children, id, entry.id))) {
return deeperParentId;
}
}
// No match was found
return null;
}
console.log(getParentId(array, 3));
console.log(getParentId(array, 5));
console.log(getParentId(array, 6));
console.log(getParentId(array, 8));
console.log(getParentId(array, 2));
Note that I overcommented it, which is not such a good idea when writing actual code; this is just for the answer.
Also, as I mentioned in the comments, please share your attempts next time.

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 Build a Dynamic object on JS From two arrays

I'm trying to build an array of objects from another two arrays, can someone help me out.
const parent =[
{ Id: 1, Cate: 'Accommodation', p_id: null },
{ Id: 4, Cate: 'National Travel', p_id: null }
]
const child =[
{ Id: 2, Cate: 'Hotel Accommodation', p_id: 1 },
{ Id: 3, Cate: 'Own arrangement', p_id: 1 },
{ Id: 5, Cate: 'Air', p_id: 4 },
{ Id: 6, Cate: 'Bus', p_id: 4 },
{ Id: 7, Cate: 'AC Volvo', p_id: 6 },
{ Id: 8, Cate: 'Luxury Bus', p_id: 6 },
{ Id: 9, Cate: 'Bus', p_id: 6 }
]
const data = [
{
id: 1,
tittle: 'Accommodation',
subItem: [
{ id: 2, tittle: 'Hotel Accommodation', subItems: [] },
{ id: 3, tittle: 'Own arrangement', subItems: [] },
],
},
{
id: 4,
tittle: 'National Travel',
subItem: [
{ id: 5, tittle: 'Air', subItems: [] },
{
id: 6,
tittle: 'Bus',
subItem: [
{ id: 7, tittle: 'AC Volvo' },
{ id: 5, tittle: 'Air' },
{ id: 8, tittle: 'Luxury Bus' },
{ id: 9, tittle: 'Bus' },
],
},
],
},
];
parent and child are the data I get from DB, now I need to change that into something looking like data. The thing is it has to be dynamic, as subItems can go long as it needs to be, how can I make something like that?
You can maintain two objects, a parent object, and a seen object. The seen object is responsible for storing all object ids already seen. That means, for each object in your array, you add it to the seen object.
// Seen object
{
id_1: {id: id_1, ...}, /* id: 1 */
id_2: {id: id_2, ...}, /* id: 2 */
id_3: {id: id_3, ...}, /* id: 3 */
id_4: {id: id_3, ...} /* id: 4 */
}
As your parent objects come before they are used by your child objects, you can say that, when the p_id does not appear in the seen object, then the current object is a parent, and so, you can add its id to the parents object (in the above example, assume id_1 is a parent). Keep in mind that this object also gets added to seen, and so, we can create a link between the parent object in seen and the one in parents by using its reference:
// Parents object
{
id_1: /* ref: 1 */ <-- this refers to the seen[id_1] object
}
This link then allows us to change the object in seen, which would then result in the object in parents changing.
When an object is not a parent object, then it must be a child of another object we have already seen. In this example, assume that the object with id_2 is a child/subItem of id_1. We can first find id_2's parent object (ie: the object with id_1) by looking it up in the seen object using seen[p_id]. Once we have the parent object for id_2, we can add a subItem array to it if needed, and add a reference to our id_2 object to this array:
// Seen object
{
id_1: {id: id_1, ..., subItem: [/* ref: 2 */]}, /* id: 1 */
id_2: {id: id_2, ...}, /* id: 2 */
}
we add id_2 as a reference by pushing in the object we added to seen by using .push(seen[Id]). We're adding a reference, as this will mean that if we change the object stored at id_2 in our seen object, then its changes will be reflected in id_1's subItem array.
Once we have iterated over all objects in our array, we can grab the values of our parents object, which contains references to the objects in seen, and those objects themselves also (may) contain references to other objects in seen. This gives us our final result:
const parent =[ { Id: 1, Cate: 'Accommodation', p_id: null }, { Id: 4, Cate: 'National Travel', p_id: null } ];
const child = [ { Id: 2, Cate: 'Hotel Accommodation', p_id: 1 }, { Id: 3, Cate: 'Own arrangement', p_id: 1 }, { Id: 5, Cate: 'Air', p_id: 4 }, { Id: 6, Cate: 'Bus', p_id: 4 }, { Id: 7, Cate: 'AC Volvo', p_id: 6 }, { Id: 8, Cate: 'Luxury Bus', p_id: 6 }, { Id: 9, Cate: 'Bus', p_id: 6 } ];
const buildChildren = arr => {
const parents = {}, seen = {};
for(const {Id, Cate, p_id} of arr) {
seen[Id] = {id: Id, title: Cate};
if(p_id in seen) {
const myParent = seen[p_id];
myParent.subItem = myParent.subItem || []; // add subItem property if it doesn't exist yet
myParent.subItem.push(seen[Id]);
} else { // Adding a new parent
parents[Id] = seen[Id];
}
}
return Object.values(parents);
}
console.log(buildChildren([...parent, ...child]));
The more concise and another detailed explanation (Based on #Nick
Parsons's idea)
Besides, the problem is that item.Id make sure that it should be sorted ascending.
The main ideas are:
Push the "parent item" into result.
//If the first meet item with `parent ID` doesn't exist in store yet, then it should be the parent item
if(!parentItemInStore)
result.push(store[Id]); // Adding a new parent
Add subItem into the "parent item". By using the Reference Javascript, we can modify the item as well as reflect the reference item.
else { // add subItem
parentItemInStore.subItem ??= []; // Using `logical nullish assignment` to initialize subItem
parentItemInStore.subItem.push(store[Id]);
}
const parent =[ { Id: 1, Cate: 'Accommodation', p_id: null }, { Id: 4, Cate: 'National Travel', p_id: null } ];
const child = [ { Id: 2, Cate: 'Hotel Accommodation', p_id: 1 }, { Id: 3, Cate: 'Own arrangement', p_id: 1 }, { Id: 5, Cate: 'Air', p_id: 4 }, { Id: 7, Cate: 'AC Volvo', p_id: 6 }, { Id: 6, Cate: 'Bus', p_id: 4 }, { Id: 8, Cate: 'Luxury Bus', p_id: 6 }, { Id: 9, Cate: 'Bus', p_id: 6 } ];
const buildHierarchyCollection = flatItems => {
flatItems.sort((a, b) => a.Id - b.Id); // this line is extremely important
const result = [], store = {};
for(const {Id, Cate, p_id} of flatItems) {
store[Id] = {Id, Cate}; // Store each item to keep the reference
const parentItemInStore = store[p_id];
if(!parentItemInStore) // If the first meet item with `parent ID` doesn't exist in store yet, then it should be the parent item
result.push(store[Id]); // Adding a new parent
else { // add subItem
parentItemInStore.subItem ??= []; // Using `logical nullish assignment` to initialize subItem
parentItemInStore.subItem.push(store[Id]);
}
}
return result;
}
console.log(buildHierarchyCollection([...parent, ...child]));
Solution 2: No need to sort
const parent =[ { Id: 1, Cate: 'Accommodation', p_id: null }, { Id: 4, Cate: 'National Travel', p_id: null } ];
const child = [ { Id: 2, Cate: 'Hotel Accommodation', p_id: 1 }, { Id: 3, Cate: 'Own arrangement', p_id: 1 }, { Id: 5, Cate: 'Air', p_id: 4 }, { Id: 7, Cate: 'AC Volvo', p_id: 6 }, { Id: 6, Cate: 'Bus', p_id: 4 }, { Id: 8, Cate: 'Luxury Bus', p_id: 6 }, { Id: 9, Cate: 'Bus', p_id: 6 } ];
const resolveParrentMissing = (store, currentItem) => {
for(let [key, value] of Object.entries(store))
if(value.p_id == currentItem.Id) {
currentItem.subItem ??= [];
currentItem.subItem.push(value);
}
}
const buildHierarchyCollection = flatItems => {
const result = [], store = {};
for(const {Id, Cate, p_id} of flatItems) {
store[Id] = {Id, Cate, p_id}; // Store each item to keep the reference
const parentItemInStore = store[p_id];
if(!p_id) // If the first meet item with `parent ID` doesn't exist in store yet, then it should be the parent item
result.push(store[Id]); // Adding a new parent
else if(parentItemInStore) { // add subItem
parentItemInStore.subItem ??= []; // Using `logical nullish assignment` to initialize subItem
parentItemInStore.subItem.push(store[Id]);
}
resolveParrentMissing(store, store[Id]);
}
return result;
}
const result = buildHierarchyCollection([...parent, ...child]);
console.log(result.map(({p_id, ...rest}) => rest));
I think this approach is simpler than many others presented here.
const makeForest = (xs, id) =>
xs .filter ((x) => x.p_id == id)
.map (({Id, Cate, p_id}, _, __, subItems = makeForest (xs, Id)) => ({
id: Id, tittle: Cate, ... (subItems.length ? {subItems} : {})
}))
const parent = [{Id: 1, Cate: 'Accommodation', p_id: null}, {Id: 4, Cate: 'National Travel', p_id: null}]
const child = [{Id: 2, Cate: 'Hotel Accommodation', p_id: 1}, {Id: 3, Cate: 'Own arrangement', p_id: 1}, {Id: 5, Cate: 'Air', p_id: 4}, {Id: 6, Cate: 'Bus', p_id: 4}, {Id: 7, Cate: 'AC Volvo', p_id: 6}, {Id: 8, Cate: 'Luxury Bus', p_id: 6}, {Id: 9, Cate: 'Bus', p_id: 6}]
console .log (
makeForest ([...parent, ...child])
)
.as-console-wrapper {max-height: 100% !important; top: 0}
We start by combining the two arrays outside our main call. This sort of code is easier to work with on a single list.
We filter out those items whose p_ids match an input parameter (and if we don't pass one, then the ones with null values will also match. Then for each one of those, we build a new object, with subItems being created with a recursive all, using the same list and the current id.
If there are other fields we want to copy over directly, we can just destructure out a ...rest parameter and include it in our mapping:
const makeForest = (xs, id) =>
xs .filter ((x) => x.p_id == id)
.map (({Id, Cate, p_id, ...rest}, _, __, subItems = makeForest (xs, Id)) => ({
id: Id, tittle: Cate, ... rest, ... (subItems.length ? {subItems} : {})
}))
Update
Nguyễn Văn Phong pointed out this simplification:
const makeForest = (xs, id) =>
xs .filter ((x) => x.p_id == id)
.map (({Id, Cate, p_id, subItems = makeForest (xs, Id)}) => ({
id: Id, tittle: Cate, ... (subItems.length ? {subItems} : {})
}))
which would work equally well with the ...rest case. It's very nice not to need those placeholder variables for the map call.
const parent =[ { Id: 1, Cate: 'Accommodation', p_id: null }, { Id: 4, Cate: 'National Travel', p_id: null } ];
const child = [ { Id: 2, Cate: 'Hotel Accommodation', p_id: 1 }, { Id: 3, Cate: 'Own arrangement', p_id: 1 }, { Id: 5, Cate: 'Air', p_id: 4 }, { Id: 6, Cate: 'Bus', p_id: 4 }, { Id: 7, Cate: 'AC Volvo', p_id: 6 }, { Id: 8, Cate: 'Luxury Bus', p_id: 6 }, { Id: 9, Cate: 'Bus', p_id: 6 } ];
function prepardata(parent, child) {
// map to hold all the values of parent and child arrays
const map = {};
// map to hold final result
let item = {};
// add parent elements to map
for (let i = 0; i < parent.length; i++) {
map[parent[i].Id] = parent[i];
}
// add child elements to map
for (let i = 0; i < child.length; i++) {
// adding empty subItems array to every child category
child[i]["subItems"] = [];
map[child[i].Id] = child[i];
}
/**
* At this point we have map ready with key as Id and value as
* the object.
* Using for-in to iterate over the map where key is Id
*/
for (let key in map) {
// if map has a key with value of the current entry p_id (map[key].p_id))
if (map.hasOwnProperty(map[key].p_id)) {
let parentCat = map[map[key].p_id]; // parent category element is the entry in map with value equal to p_id
let childCat = map[key]; // child category element is the entry in map with value equal to key
// Elements with p_id as null should be at the top level
if (!parentCat.p_id) {
// If final item map does not have an entry, then add it
if (!item[parentCat.Id]) {
item[parentCat.Id] = {
id: parentCat.Id,
title: parentCat.Cate,
subItems: [childCat]
}
} else {
// else just push the element in the existing parent categories subitems
item[parentCat.Id]["subItems"].push(childCat);
}
} else {
// Else it's a sub category, the item needs to be pushed under its subItems array
if (item[parentCat.p_id]) {
item[parentCat.p_id]["subItems"].filter(s => s.Id === childCat.p_id)[0]["subItems"].push(childCat);
}
}
}
}
// finally return the values from item map.
return Object.values(item);
}
const data = prepardata(parent, child);
console.log(data);
You can use javascript map and filter to achieve this result. Iterate over the parent array to get the id and title and filter the child array to get only items with matching p_id.
const data = [];
parent.map(p => {
const item = {
id: p.Id,
title: p.Cate,
subItem: child.filter(c => c.p_id === p.Id)
}
data.push(item);
});
Edit
I've added snippet with the updated solution which supports multiple levels as my initial answer only supported 2 levels. Accepted answer by #Nick Parsons is correct and also shorter version but I'm still updating this answer to complete it.

Javascript re-order array of object by value

How do I re-order array of object showing below by follow value. If follow value is not -1, move the item below to the item that has the id value same as follow value.
Here is the example.
let charObj = [
{ id: 8, name: 'Catelyn Stark', follow: -1 },
{ id: 7, name: 'Jaime Lannister', follow: 8 },
{ id: 3, name: 'Jon Snow', follow: -1 },
{ id: 4, name: 'Daenerys Targaryen', follow: 7 },
{ id: 5, name: 'Sansa Stark', follow: 4 }
];
Expected output will be;
let charObj = [
{ id: 8, name: 'Catelyn Stark', follow: -1 },
{ id: 7, name: 'Jaime Lannister', follow: 8 },
{ id: 4, name: 'Daenerys Targaryen', follow: 7 },
{ id: 5, name: 'Sansa Stark', follow: 4 },
{ id: 3, name: 'Jon Snow', follow: -1 }
];
Not sure if I can use sort(). What is the best way to re-order this object?
I think this will do what you're asking. I'm sure it could be made more efficient, but unless your list gets quite large that shouldn't make much practical difference. Also, this assumes any character will only have one follower. If that's not the rule, then the function will have to be adjusted.
let charObj = [
{ id: 8, name: "Catelyn Stark", follow: -1 },
{ id: 7, name: "Jaime Lannister", follow: 8 },
{ id: 3, name: "Jon Snow", follow: -1 },
{ id: 4, name: "Daenerys Targaryen", follow: 7 },
{ id: 5, name: "Sansa Stark", follow: 4 }
];
function sortChars(chars) {
let result = [];
let leaders = chars.filter(c => c.follow === -1);
for (let i = 0; i < leaders.length; i++) {
let current = leaders[i];
while (current) {
result.push(current);
let next = charObj.find(c => c.follow === current.id);
current = next;
}
}
return result;
}
console.log(sortChars(charObj));

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

Categories

Resources