最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Remove matched object from deeply nested array of objects - Stack Overflow

programmeradmin3浏览0评论

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.

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.

Share Improve this question asked Mar 9, 2019 at 23:20 DrCordDrCord 3,9553 gold badges36 silver badges47 bronze badges 5
  • Could you provide what would be the desired result in the application of the function for an example case? – Rodrigo Rodrigues Commented Mar 9, 2019 at 23:29
  • 1 having both checks of curNode.name === clickedTitle and child => child.name === clickedTitle doesn't make sense to me. – Farshid Zaker Commented Mar 9, 2019 at 23:31
  • Exacty, that´s why I am asking for an example of the desired effect – Rodrigo Rodrigues Commented Mar 9, 2019 at 23:35
  • the desired result is to remove the item from the original tree. – DrCord Commented Mar 10, 2019 at 14:26
  • ggorlen's answer worked without any issues. Victor's answer did seem to work but the application wasn't working out, was causing weird behaviors. – DrCord Commented Mar 10, 2019 at 14:27
Add a ment  | 

5 Answers 5

Reset to default 2

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 remend 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/[email protected]"></script>

Disclaimer: I'm the author of object-scan

发布评论

评论列表(0)

  1. 暂无评论