Nolan
Nolan

Reputation: 916

JS | lodash : recursive delete from deep nested array

Below is a recursive method to delete a comment from a deeply nested array. The code works, but here are my question:

QUESTION:

  1. I'm using _.remove within the loop to find and remove a comment in the current array. It seems expensive for obvious reasons i.e. loop within a loop, but other approaches seems just as expensive. I'm sure there are better ways to do this.

WORKING EXAMPLE:

https://plnkr.co/edit/PeW5ZFLynO2q8VNqbAHx?p=preview

    var comments = [
        {
            id: 1,
            depth: 0,
            subject: 'Subject one'
        },
        {
            id: 2,
            depth: 0,
            subject: 'Subject two',
            children: [
                {
                    id: 3,
                    depth: 1,
                    subject: 'Subject two dot one'
                },
                {
                    id: 4,
                    depth: 1,
                    subject: 'Subject two dot two'
                }
            ]
        },
        {
            id: 5,
            depth: 0,
            subject: 'Subject three',
            children: [
                {
                    id: 6,
                    depth: 1,
                    subject: 'Subject three dot one'
                },
                {
                    id: 7,
                    depth: 1,
                    subject: 'Subject three dot two',
                    children: [
                        {
                            id: 8,
                            depth: 2,
                            subject: 'Subject three dot two dot one'
                        },
                        {
                            id: 9,
                            depth: 2,
                            subject: 'Subject three dot two dot two'
                        }
                    ]
                }
            ]
        }
    ];

    function deleteComment(comment, comments) {
        var self = this,
            db = [];

        function removeComment(items, parent) {
            _.forEach(items, function (item) {

                // QUESTION - seems expensive as we have a loop in a loop
                _.remove(items, function(item) {
                    if (item.id === comment.id) {
                      console.log(item);
                      return true;
                    }
                    // NOTE: use above for demo purposes
                    // return item.id === comment.id
                });

                _.has(item, 'children')  ? removeComment(item.children, item) : 0;
            });
        }

      removeComment(comments, db);

    }

    var commentToBeDeleted = {           
        id: 8,
        depth: 2,
        subject: 'Subject three dot two dot one'
      };

    deleteComment(commentToBeDeleted, comments);

Upvotes: 1

Views: 1174

Answers (2)

Matt
Matt

Reputation: 4752

I am not sure if this is the most performant way to accomplish this, but this is the most succinct way I have found:

It turns out JSON.stringify provides a callback for each visited JSON value being converted, which you can use to determine if the value should be included in the string. You can use this to visit each value without having to do the traversing yourself.

From MDN

The replacer parameter can be either a function or an array. As a function, it takes two parameters, the key and the value being stringified. The object in which the key was found is provided as the replacer's this parameter. Initially it gets called with an empty key representing the object being stringified, and it then gets called for each property on the object or array being stringified. It should return the value that should be added to the JSON string,

In your case the function would look something like

function deleteComment(commentToBeDeleted, comments) {
    return JSON.parse(JSON.stringify(comments, function(key, value) {
        if (commentToBeDeleted.id !== value.id) {
            return value;
        } 
    }));
}

Note: you probably don't want to use this code as is, as it leaves an empty node, but, you can insert what ever you logic you like into the callback, and this should get you started.

Upvotes: 1

Kyle Peacock
Kyle Peacock

Reputation: 31

You could probably find a way to do this more efficiently with a .reduce() function to combine .forEach and _.remove. However, if the code works, it works!

Upvotes: 1

Related Questions