0

I'm trying to deep filter until specific object with id found. I've taken these references,

  1. Recursively filter array of objects
  2. Filter items in array of objects
  3. Recursively filter array of objects with different properties
  4. Remove Object from Array using JavaScript

I am able to find the parent node, But was unable to delete specific children. What I'm doing wrong here?

Payload,

[
  {
    id: 1,
    name: "Shrek",
    lock: false,
    checked: false,
    selected: false,
    layers: [],
  },
  {
    id: 2,
    name: "Fiona",
    lock: false,
    checked: false,
    selected: false,
    layers: [
      {
        id: 4,
        name: "Lord Farquad",
        lock: false,
        checked: false,
        selected: false,
        layers: [
          {
            id: 5,
            name: "Prince Charming",
            lock: false,
            checked: false,
            selected: false,
            layers: [],
          },
        ],
      },
    ],
  },
  {
    id: 3,
    name: "Donkey",
    lock: false,
    checked: false,
    selected: false,
    layers: [],
  },
]

I'm getting Parent node with this code, but not sure from where to splice.

this.layers.filter(function f(o: any) {
  if (o.id == 5) return true;
  if (o.layers) return (o.layers = o.layers.filter(f)).length;
});

Expecting Output:

[
  {
    id: 1,
    name: "Shrek",
    lock: false,
    checked: false,
    selected: false,
    layers: [],
  },
  {
    id: 2,
    name: "Fiona",
    lock: false,
    checked: false,
    selected: false,
    layers: [
      {
        id: 4,
        name: "Lord Farquad",
        lock: false,
        checked: false,
        selected: false,
        layers: [],
      },
    ],
  },
  {
    id: 3,
    name: "Donkey",
    lock: false,
    checked: false,
    selected: false,
    layers: [],
  },
]
Keyur
  • 1,113
  • 1
  • 23
  • 42
  • 1
    So you want to delete objects with a given id. You also speak of filtering... but I don't see that in the expected output? Or do you mean the same thing with filtering as with as deleting? – trincot Apr 08 '21 at 13:13
  • 1
    Please [edit] your question to put the data and the code together into a [mre]. You can use Stack Snippets (icon looks like `<>` in the editor toolbar) to make it runnable. – Heretic Monkey Apr 08 '21 at 13:20

3 Answers3

1

you were close.

  • based on your expected output I think you want to return o.id != 5
  • if a node has layers then you need to assign those layers to the filtered value of themselves o.layers = o.layers.filter(f)
function f(o) {
  if (o.layers.length) {
    o.layers = o.layers.filter(f)
  }
  return o.id != 5;
}

let filtered = layers.filter(f);
stackoverfloweth
  • 6,669
  • 5
  • 38
  • 69
1

I would try to build this on top of a reusable function. I abstracted a deepFilter from another answer so that we can configure the name of the child nodes (most commonly 'children', here 'layers', but I've seen many others.) The returned function takes a predicate and returns another function which takes an array, and recursively keeps only those nodes for which the predicate returns true.

Using this, we can write removeById by passing 'layers' to deepFilter along with a predicate that checks if a node's id fails to match our target. It looks like this

const deepFilter = (childProp) => (pred) => (xs) =>
  xs .flatMap (({[childProp]: children = [], ...rest}) => 
    pred (rest)
      ? [{... rest, [childProp]: deepFilter (childProp) (pred) (children)}]
      : []
  )

const removeById = (target) => 
  deepFilter ('layers') (({id}) => id !== target)

const payload = [{id: 1, name: "Shrek", lock: false, checked: false, selected: false, layers: [/* onions have layers */]}, {id: 2, name: "Fiona", lock: false, checked: false, selected: false, layers: [{id: 4, name: "Lord Farquad", lock: false, checked: false, selected: false, layers: [{id: 5, name: "Prince Charming", lock: false, checked: false, selected: false, layers: []}]}]}, {id: 3, name: "Donkey", lock: false, checked: false, selected: false, layers: [/* parfaits have layers */]}]

console .log (
  removeById (5) (payload)
)
.as-console-wrapper {max-height: 100% !important; top: 0}

If we prefer to call in this manner: removeById (payload, 5), then we can simply write

const removeById = (payload, target) => 
  deepFilter ('layers') (({id}) => id !== target) (payload)

The big advantage here is that we can write a reusable function in a simple manner, and find uses all over the place for it, making our custom code extremely trivial.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
1

Here is an iterative solution using object-scan

// const objectScan = require('object-scan');

const data = [{ id: 1, name: 'Shrek', lock: false, checked: false, selected: false, layers: [] }, { id: 2, name: 'Fiona', lock: false, checked: false, selected: false, layers: [{ id: 4, name: 'Lord Farquad', lock: false, checked: false, selected: false, layers: [{ id: 5, name: 'Prince Charming', lock: false, checked: false, selected: false, layers: [] }] }] }, { id: 3, name: 'Donkey', lock: false, checked: false, selected: false, layers: [] }];

const remove = objectScan(['**(^layers$)'], {
  rtn: 'bool',
  abort: true,
  useArraySelector: false,
  filterFn: ({ parent, property, value, context }) => {
    if (value.id === context) {
      parent.splice(property, 1);
      return true;
    }
    return false;
  }
});

console.log(remove(data, 5));
// => true

console.log(data);
// => [ { id: 1, name: 'Shrek', lock: false, checked: false, selected: false, layers: [] }, { id: 2, name: 'Fiona', lock: false, checked: false, selected: false, layers: [ { id: 4, name: 'Lord Farquad', lock: false, checked: false, selected: false, layers: [] } ] }, { id: 3, name: 'Donkey', lock: false, checked: false, selected: false, layers: [] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@14.3.1"></script>

Disclaimer: I'm the author of object-scan

vincent
  • 1,953
  • 3
  • 18
  • 24