Underscore.js findWhere nested objects

2019-01-15 08:28发布

问题:

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?

回答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);

}


回答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.



回答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



回答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).