Underscore.js findWhere nested objects

36,786

Solution 1

This isn't the prettiest code, but I tested it out and it seems to work the way you are asking. It's setup as a lodash/underscore mixin, but can be used however. Usage would be like this:

_.findDeep(testItem, { 'path': 'folderName/sub-child.html' })

Implementation:

findDeep: function(items, attrs) {

  function match(value) {
    for (var key in attrs) {
      if(!_.isUndefined(value)) {
        if (attrs[key] !== value[key]) {
          return false;
        }
      }
    }

    return true;
  }

  function traverse(value) {
    var result;

    _.forEach(value, function (val) {
      if (match(val)) {
        result = val;
        return false;
      }

      if (_.isObject(val) || _.isArray(val)) {
        result = traverse(val);
      }

      if (result) {
        return false;
      }
    });

    return result;
  }

  return traverse(items);

}

Solution 2

Instead of findWhere, use filter, which takes a function as the predicate rather than a key-value map. Use a recursive function to check the current node and possible children. Something like this:

var searchText = './about2.html';

var recursiveFilter = function(x) {
    return x.path == searchText || 
        ( typeof x.children != 'undefined' && recursiveFilter(x.children['sub-child.html']) );
};

_.filter(files, recursiveFilter);

Edit

Assuming this works, you'll probably want to make a function getRecursiveFilter(searchText). Here's how that would look:

function getRecursiveFilter(searchText) { 
    var recursiveFilter = function(x) {
        return x.path == searchText || 
            (typeof x.children != 'undefined' 
                && arguments.callee(x.children['sub-child.html']) );
    };
    return  recursiveFilter;
}

Note that here, recursiveFilter uses arguments.callee to call itself recursively.


Here's a working demo.

Solution 3

This already has an accepted answer, but this other answer was very clean and perfect for my similar situation: https://stackoverflow.com/a/21600748/1913975 _.filter +_.where

Solution 4

Though accepted answer works, it's too generic - it searches all the properties of an object to find children. I am proposing introducing an extra parameter, called 'recursProperty' which will be considered to go deep in the object. This solution is also setup to be used as lodash/underscore mixin and extends loadash/underscore capabilities.

_.findDeep = function(collection, predicate, recursProperty){
    let items = [];
    _.each(collection, each => items.push(each));
    return _.find(items, function(value, key, coll){
        if (predicate(value, key, coll)){
            return true;
        } else {
            _.each(value[recursProperty], each => items.push(each));
        }
    });
};

It can be used as any other underscore function. e.g,

_.findDeep(self.baseEntities, baseEntity => baseEntity.id === 71, 'entity');

Not providing proper value for 'recursProperty' argument or providing null/undefined will simply make the search only on first level (no going deep).

Share:
36,786
wesbos
Author by

wesbos

Author of Sublime Text Book HTML5, JavaScript, Sublime Text, Node.js, CSS3 and all good things.

Updated on April 18, 2020

Comments

  • wesbos
    wesbos about 4 years

    I have an object of folders/files that looks like this:

    {
      about.html : {
        path : './about.html'
      },
      about2.html : {
        path : './about2.html'
      },
      about3.html : {
        path : './about3.html'
      },
      folderName : {
        path : './folderName',
        children : {
          sub-child.html : {
            path : 'folderName/sub-child.html'
          }
        }
      }
    }
    

    And it can go 6-7 levels deep of folders having children.

    I want to find the object where path is equal to a string that I provide. Regardless of how deep it is.

    I'm using underscore which only does top level:

    _.findWhere(files,{path:'./about2.html'}
    

    How can I do a deep, nested search. Does underscore have something for this or do I need to build a mixin with recursion?