4

I have a filter function, which uses filter to quickly search a text in an array:

filtered = filtered.filter((row) => {
   return Object.keys(row).some((key) => {
        return String(row[key]).toLowerCase().indexOf(this.quickSearch.toLowerCase()) > -1
      })
   })

This works great for single level array, but not sure how to adapt it to work down unknown number of levels of array of objects like this

{
   'name': 'james',
   'post': {
        'name': 'my favorite teams'
    }
}

The code above finds james, no problem, but it will not find teams as its not going deep enough.

Naturally I don't want to hardcode something like if row[key] == 'post', because I'm using this code for multiple data sources and need it to be dynamic.

How do I adapt this to work in multi level arrays like the example above?

4
  • I don't understand how that code would work with the example input you provided. The example you've shown is an object, not an array. The filter method can only be used on arrays. Commented Dec 14, 2017 at 23:42
  • sorry the example object above is within the array Commented Dec 14, 2017 at 23:45
  • Is there only one level down (like the example) or there could be more? Commented Dec 14, 2017 at 23:50
  • one level of array, but multiple objects within objects - sorry I mislabelled the question Commented Dec 14, 2017 at 23:51

2 Answers 2

4

If there are many levels, then recursion is the best solution:

let searchString = this.quickSearch.toLowerCase();                       // do this only once instead of calling toLowerCase over and over again, besides we are using a regular function (not an arrow one) so "this" will be messed up anyways
filtered = filtered.filter(function search(row) {                        // name the function so we can use recursion (thus we can't use an arrow function)
   return Object.keys(row).some((key) => {                               // ...
       if(typeof row[key] === "string") {                                // if the current property is a string
           return row[key].toLowerCase().indexOf(searchString) > -1;     // then check if it contains the search string
       } else if(row[key] && typeof row[key] === "object") {             // oterwise, if it's an object
           return search(row[key]);                                      // do a recursive check
       }
       return false;                                                     // return false for any other type (not really necessary as undefined will be returned implicitly)
   });
});
Sign up to request clarification or add additional context in comments.

1 Comment

just like magic! exactly what I was after with explanations too! thanks
2

You could use a recursive function that calls itself on the values of objects and arrays (using Object.values and Array#some):

const containsDeep = (text) => (value) => {
  if(!value) return false;
  const valueType = typeof value;
  
  if(valueType === 'string') {
    return value.toLowerCase().indexOf(text.toLowerCase()) > -1;
  }
  if(Array.isArray(value)) {
    return value.some(containsDeep(text));
  }
  if(valueType === 'object') {
    return Object.values(value).some(containsDeep(text));
  }
  return false;
};

const data = [
  {
   'name': 'bar',
   'post': {
      'name': 'my favorite teams'
    }
  },
  {
    'name': 'fizz',
    'posts': [{'name': 'buzz'}]
  },
  {
    'name': 'bla',
    'post': {
      'name': 'blee',
      'comments': [null, {'name': 'bar'}]
    }
  },
  {
   'name': 'foo',
   'post': {
      'name': 'bar'
    }
  }
];

const result = data.filter(containsDeep('bar'));
console.log(result);

3 Comments

typeof null === "object".
if(!value) return false; I specifically added a case where null is encountered (third element of data).
Ah! I didn't see that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.