ohh2ahh
ohh2ahh

Reputation: 385

Delete objects recursively in multidimensional array based on key value

I've been breaking my head for hours to find out how I can delete objects recursively in multidimensional array based on key value. I need to delete all objects containing exclude: true in the example array below. I tried looping the items with a recursion function but I'm struggeling with splicing the items out of the array...

// example multidimensional array with objects
let array: Object[] = [
    // exclude on level 1
    {
        name: 'A',
        exclude: true,
        content: [
            {
                name: 'A-A',
            },
            {
                name: 'A-B',
                exclude: true,
            },
            {
                name: 'A-C',
                content: [
                    {
                        name: 'A-C-A',
                    }
                ]
            }
        ]
    },
    // exclude on level 2
    {
        name: 'A',
        content: [
            {
                name: 'A-A',
            },
            {
                name: 'A-B',
                exclude: true,
            },
            {
                name: 'A-C',
                content: [
                    {
                        name: 'A-C-A',
                    }
                ]
            }
        ]
    },
    // exclude on level 2 and 3
    {
        name: 'A',
        content: [
            {
                name: 'A-A',
            },
            {
                name: 'A-B',
                exclude: true,
            },
            {
                name: 'A-C',
                content: [
                    {
                        name: 'A-C-A',
                        exclude: true,
                    }
                ]
            }
        ]
    }
]

// run function
deleteItems(array);

// path is an array containing the indexes of the items
// e.g. path = [0, 0, 0] --> array[0][0][0]
function deleteItems(arr, path_index: number = 0, path: any[] = [0]): void {
    // loop through arr
    for (let i = 0; i < arr.length; i++) {
        // get item
        let item = arr[i];

        // set path
        path[path_index] = i;

        // delete here somehow the item with corresponding path
        if (item['exclude']) {
            console.log('path', path);
        }

        // recursion
        if ('content' in item) {
            // +2 to path index (1. for content, 2. for i)
            let path_index_ref = path_index + 2;
            // create new path so old path does not get changed
            let path_ref = path.slice();
            path_ref.push('content');

            this.deleteFlowchartItem(item['content'], path_index_ref, path_ref);
        } // if content
    } // for
} // deleteFlowchartItem()

Upvotes: 2

Views: 615

Answers (2)

Ori Drori
Ori Drori

Reputation: 191976

Splicing items out of the array changes the length of the array, and makes iterating with for loops difficult. The usual solution is to iterate the arrays backwards, which makes the length irrelevant (0 index is the stop point).

However, I suggest a different approach, rebuild the entries tree recursively without the items you wish to exclude using Array#reduce. If the an object contains a sub array (according to predefined childrenProp), a new object is created using Object#assign, and the sub array is add after filtering.

const array = [{"name":"A","exclude":true,"content":[{"name":"A-A"},{"name":"A-B","exclude":true},{"name":"A-C","content":[{"name":"A-C-A"}]}]},{"name":"A","content":[{"name":"A-A"},{"name":"A-B","exclude":true},{"name":"A-C","content":[{"name":"A-C-A"}]}]},{"name":"A","content":[{"name":"A-A"},{"name":"A-B","exclude":true},{"name":"A-C","content":[{"name":"A-C-A","exclude":true}]}]}];

const recursiveFilter = (arr, predicate, childrenProp) => arr.reduce((a, o) => {
  // if predicate fails don't include the object and it's children
  if(!predicate(o)) {
    return a;
  }

  const obj = Array.isArray(o[childrenProp]) ? // if there is a sub array
    Object.assign({}, o, { // create a new object from the original properties with the filtered sub array
      [childrenProp]: recursiveFilter(o[childrenProp],  predicate, childrenProp)
    }) 
    : 
    o; // or use the original object

  a.push(obj);

  return a;
}, []);

const result = recursiveFilter(array, ({ exclude }) => !exclude, 'content');

console.log(result);

Upvotes: 4

guest271314
guest271314

Reputation: 1

You can use a function to check if "exclude" property is defined, if true delete the element at that , else recursively iterate "content" properties of objects, call delete where "exclude" is true, use JSON.stringify(), JSON.parse() and String.prototype.replace() to remove empty indexes cast to null from array

let array = [{"name":"A","exclude":true,"content":[{"name":"A-A"},{"name":"A-B","exclude":true},{"name":"A-C","content":[{"name":"A-C-A"}]}]},{"name":"A","content":[{"name":"A-A"},{"name":"A-B","exclude":true},{"name":"A-C","content":[{"name":"A-C-A"}]}]},{"name":"A","content":[{"name":"A-A"},{"name":"A-B","exclude":true},{"name":"A-C","content":[{"name":"A-C-A","exclude":true}]}]}];

const fn = (o, arr, x = "exclude", it = "content") => {
  if (o[x] === true) {
    delete arr[arr.findIndex(obj => JSON.stringify(obj) === JSON.stringify(o))];
  } else {
    if (Array.isArray(o[it])) {
      o.content.forEach(obj => fn(obj, o[it]))
    }
  }
}

array.forEach(o => fn(o, array));

array = JSON.parse(JSON.stringify(array).replace(/null,|null\b/g, ""));

console.log(array);

Upvotes: 0

Related Questions