-1

I've got an array of nested objects

var arr = [{
  tires: 2,
  exterior: {
    color: 'white',
    length: 2,
    width: 1
  }
},{
  tires: 4,
  exterior: {
    color: 'blue',
    length: 5,
    width: 3
  }
},{
  tires: 4,
  exterior: {
    color: 'white',
    length: 2,
    width: 3
  }
}];

I want to create a function such that:

var findItems = function(arr, value){
  // return array of found items
};

Some examples:

findItems(arr, 'white');                    // [ arr[0], arr[2] ]
findItems(arr, 2);                          // [ arr[0], arr[2] ]
findItems(arr, {tires: 2});                 // [ arr[0] ]
findItems(arr, {tires: 2, color: 'white'}); // [  ]
findItems(arr, {width: 1, color: 'white'}); // [ arr[0] ]

It's easy enough to find values for arrays with non-nested objects or if you know the exact level that you want to search in. But I'm not sure how to go about just finding "any value, anywhere" in an array. I quickly get into looping hell.

I can use Underscore if that helps.

Oriol
  • 274,082
  • 63
  • 437
  • 513
fuzzybabybunny
  • 5,146
  • 6
  • 32
  • 58
  • Well.. what if "tires" was in exterior, would you need somehow a mechanism to ignore it? – axelduch Oct 04 '15 at 00:34
  • http://stackoverflow.com/questions/2549320/looping-through-an-object-tree-recursively – Peter Scott Oct 04 '15 at 00:36
  • @axelduch, no, not at this point. Just a direct "does this array contain this value, anywhere?" and then returning the array item where it appears. – fuzzybabybunny Oct 04 '15 at 00:36
  • @Oriol, it'll simply try and find the object in the array of objects. If it finds it, it'll return the array item(s) with that object. Like in my `findItems(myArray, { tires: 2 })` example. – fuzzybabybunny Oct 04 '15 at 00:38
  • @Oriol, oh, I see. Uhhh... it shouldn't return anything. – fuzzybabybunny Oct 04 '15 at 00:42
  • Hmmm... I can see that searching for objects in objects is not at all as straightforward as initially thought. It would be nice if `findItems(myArray, { width:1, color:'white' })` returned the first item. – fuzzybabybunny Oct 04 '15 at 00:49
  • Use `for` to iterate over _indices_ of an _Array_ and `for..in` to iterate over _keys_ of an _Object_ – Paul S. Oct 04 '15 at 01:18

1 Answers1

1

I think it would be something like this:

function isObject(val) {
  return Object(val) === val;
}
function search(val, ctx) {
  var valIsObject = isObject(val);
  return (function search(context) {
    if(!isObject(context)) return val === context;
    if(valIsObject && Object.keys(val).every(function(key) {
      return context[key] === val[key];
    })) return true;
    return Object.keys(context).some(function(key) {
      return search(context[key]);
    });
  })(ctx);
}
function findItems(arr, value){
  return arr.filter(function(item) {
    return search(value, item);
  });
}

It should work reasonably well, except in some edge cases like circular references (infinite recursion) or descriptor properties (the code may call the getter on an incompatible object).

Oriol
  • 274,082
  • 63
  • 437
  • 513
  • Wow, thanks so much. Now I gotta sit down and try and figure out why this code is working. In general I still have a very tough time thinking through things with recursion. – fuzzybabybunny Oct 04 '15 at 04:34
  • everything seems to work ok except in the case where the array of objects contains a value that is 'null'. Then 'context' turns to 'null' and the function breaks. – fuzzybabybunny Oct 04 '15 at 06:43
  • @fuzzybabybunny I can't reproduce that problem. If context is null then isObject should return false, and then the code shouldn't try to access properties of null. – Oriol Oct 04 '15 at 13:48