Kevin Collins
Kevin Collins

Reputation: 1461

Remove item at any level in JS nested object array

Given an array of objects like this:

var items = [{
  id: 1
}, {
  id: 2, 
  child: {
    id: 3
  }
}, {
  id: 4,
  child: {
    id: 5,
    child: {
      id: 6
    }
  }
}];

I need a method to remove an item at any level. This code does what I want, but is there a better way?

Initially I tried doing it with one recursive function, but couldn't get that to work.

var removed = removeItems(items, 5);
print(removed);

function removeItems(items, id) {
  items.forEach(function(item, index, allItems) {
    if (item.id === id) {
      items.splice(index, 1);
    }
    if (item.child) {
      item = testChild(item, item.child, id);
    }
  });
  return items;
}

function testChild(parent, child, id) {
  if (child.id === id) {
    delete parent.child
    return parent;
  } else {
    if (child.child) {
      return testChild(child, child.child, id);
    }
    return parent;
  }
}

function print(obj) {
  document.querySelector('#out').innerHTML += JSON.stringify(obj, null, 2);
}

jsfiddle: https://jsfiddle.net/syvf46uL/12/

Upvotes: 1

Views: 1370

Answers (1)

EyuelDK
EyuelDK

Reputation: 3189

Here is a general purpose remove function that should work as you want.

var items = [{
    id: 1
  }, { 
    id: 2, 
    child: { id: 3 }
  }, {
    id: 4,
    child: { 
      id: 5, 
      child: { 
        id: 6 
      } 
    }
  }
];

function remove(src, predicate) {
  
  // for Array
  if (Array.isArray(src)) {
    for (var i=src.length-1; i>-1; i--) {
      if (predicate(src[i])) {
        src.splice(i, 1);
      } else {
        remove(src[i], predicate);
      }
    }
  }
  
  // for Object
  else {
    for (var i in src) {
      if (predicate(src[i])) {
        delete src[i];
      } else {
        remove(src[i], predicate);
      }
    }
  }
}

// remove id == 1
remove(items, function(element) {
  return element.id && element.id == 1;
});

console.log(JSON.stringify(items));

// remove id == 6
remove(items, function(element) {
  return element.id && element.id == 6;
});

console.log(JSON.stringify(items));

One important problem with your code, an all too common mistake, is that you attempted to shrink the array - via the splice method - while iterating forward. This will cause you to skip an element every time you remove an element.

Think of it this way, you are iterating 0->length; you remove ith element; now you previous (i + 1)th element has become your ith element; but you are still iterating 0->length thus causing you to skip the new ith element and go onto the (i + 1)th element, which was your previous (i + 2)th element. This is solved by iterating backwards length->0; shrinkage of the array doesn't affect your iterator because the shrinkage always occurs from i+1->length on wards but you are iterating from i->0.

Upvotes: 2

Related Questions