pixelmusik
pixelmusik

Reputation: 509

How can I use Lodash/JS to recursively filter nested objects?

I have an array with objects of unknown depth, like this

var objects = [{
    id: 1,
    name: 'foo'
}, {
    id: 2,
    name: 'bar',
    childs: [{
        id: 3,
        name: 'baz',
        childs: [{
            id: 4,
            name: 'foobar'
        }]
    }]
}];

I would like to be able to filter a specific child object by it's id.

Currently I am using this little lodash script (referred from this question) but it works only with objects not deeper than one level. So searching for id: 1 and id: 2 would work fine while searching for id: 3 or id: 4 would return undefined.

function deepFilter(obj, search) {
    return _(obj)
        .thru(function(coll) {
            return _.union(coll, _.map(coll, 'children'));
        })
        .flatten()
        .find(search);
}

A little JSfiddle.

Upvotes: 2

Views: 8622

Answers (4)

brk
brk

Reputation: 50291

You can have a recursive function and check for the child

var objects = [{
  id: 1,
  name: 'foo'
}, {
  id: 2,
  name: 'bar',
  childs: [{
    id: 3,
    name: 'baz',
    childs: [{
      id: 4,
      name: 'foobar'
    }]
  }]
}];
let tempArray = [];


function doRecursiveSearch(obj, id) {

  obj.forEach(function(item) {
    console.log(item)
    if (item.id === id) {
      tempArray.push(item)
    } else {
      if (item.childs && Array.isArray(item.childs)) {
        console.log(item)
        doRecursiveSearch(item.childs, id)
      }
    }

  })
}
doRecursiveSearch(objects, 4)
console.log(tempArray)

Upvotes: 0

Nikhil Aggarwal
Nikhil Aggarwal

Reputation: 28455

You need to call the function recursively in order to target child object. Try following

Iterate over the array and for each object check whether the id is found. If yes, break and return the result, else continue to search in the child (if exists).

Approach 1 : Traverses the tree branch by branch

Using this approach, first the code traverses for first element till the last child, then second element this last child and so on.

var objects = [{id: 1,name: 'foo'}, {id: 2,name: 'bar',childs: [{id: 3,name: 'baz',childs: [{id: 4,name: 'foobar'}]}]}];

function findObject(arr, id) {
  var result;
  for (let i = 0 ; i < arr.length; i++) {
    if(arr[i].id === id) {
      result = arr[i];
      break;
    }
    if(arr[i].childs) {
      result = findObject(arr[i].childs, id);
      if(result) break;
    }
  }
  return result;
}

console.log(findObject(objects, 4));

Approach 2 : Traverses the tree depth by depth

Using this approach, first the code traverses for first level elements, then second level elements and so on.

var objects = [{id: 1,name: 'foo'}, {id: 2,name: 'bar',childs: [{id: 3,name: 'baz',childs: [{id: 4,name: 'foobar'}]}]}];

function findObject(arr, id) {
  var result;
  var children = [];
  for (let i = 0 ; i < arr.length; i++) {
    if(arr[i].id === id) {
      result = arr[i];
      break;
    }
    if(arr[i].childs) {
      children = [...children, ...arr[i].childs];
    }
  }
  if(!result && children.length) {
    result = findObject(children, id);
  }
  return result;
}

console.log(findObject(objects, 4));

Upvotes: 3

Nina Scholz
Nina Scholz

Reputation: 386624

You could take an iterative and recursive approach.

function find(id, array) {
    var result;
    array.some(o => o.id === id && (result = o) || (result = find(id, o.children || [])));
    return result;
}

var objects = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar', children: [{ id: 3, name: 'baz', children: [{ id: 4, name: 'foobar' }] }] }];

console.log(find(1, objects));
console.log(find(2, objects));
console.log(find(3, objects));
console.log(find(4, objects));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 6

ibrahim mahrir
ibrahim mahrir

Reputation: 31692

You can do that recursively like so:

function deepFind(arr, search) {
    for(var obj of arr) {
        if(search(obj)) {
            return obj;
        }
        if(obj.childs) {
            var deepResult = deepFind(obj.childs, search);
            if(deepResult) {
                return deepResult;
            }
        }
    }
    return null;
}

Then use it like so:

var result = deepFind(objects, function(obj) {
    return obj.id === myId;
});

Example:

function deepFind(arr, search) {
    for(var obj of arr) {
        if(search(obj)) {
            return obj;
        }
        if(obj.childs) {
            var deepResult = deepFind(obj.childs, search);
            if(deepResult) {
                return deepResult;
            }
        }
    }
    return null;
}

var objects = [{id: 1,name: 'foo'}, {id: 2,name: 'bar',childs: [{id: 3,name: 'baz',childs: [{id: 4,name: 'foobar'}]}]}];

console.log("ID 1:", deepFind(objects, obj => obj.id === 1));
console.log("ID 4:", deepFind(objects, obj => obj.id === 4));
console.log("ID 7:", deepFind(objects, obj => obj.id === 7));

Upvotes: 3

Related Questions