57

I'm wondering what is the cleanest way, better way to filter an array of objects depending on a string keyword. The search has to be made in any properties of the object.

When I type lea I want to go trough all the objects and all their properties to return the objects that contain lea

When I type italy I want to go trough all the objects and all their properties to return the objects that contain italy.

I know there are lot of solutions but so far I just saw some for which you need to specify the property you want to match.

ES6 and lodash are welcome!

  const arrayOfObject = [{
      name: 'Paul',
      country: 'Canada',
    }, {
      name: 'Lea',
      country: 'Italy',
    }, {
      name: 'John',
      country: 'Italy',
    }, ];

    filterByValue(arrayOfObject, 'lea')   // => [{name: 'Lea',country: 'Italy'}]
    filterByValue(arrayOfObject, 'ita')   // => [{name: 'Lea',country: 'Italy'}, {name: 'John',country: 'Italy'}]
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Léo Coco
  • 4,022
  • 11
  • 52
  • 97

10 Answers10

89

You could filter it and search just for one occurence of the search string.

Methods used:

function filterByValue(array, string) {
    return array.filter(o =>
        Object.keys(o).some(k => o[k].toLowerCase().includes(string.toLowerCase())));
}

const arrayOfObject = [{ name: 'Paul', country: 'Canada', }, { name: 'Lea', country: 'Italy', }, { name: 'John', country: 'Italy' }];

console.log(filterByValue(arrayOfObject, 'lea')); // [{name: 'Lea', country: 'Italy'}]
console.log(filterByValue(arrayOfObject, 'ita')); // [{name: 'Lea', country: 'Italy'}, {name: 'John', country: 'Italy'}]
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 7
    If one of the property is not a string but a object we might want to use this `function filterByValue(array, string) { return array.filter(o => { return Object.keys(o).some(k => { if(typeof o[k] === 'string') return o[k].toLowerCase().includes(string.toLowerCase()); }); }); }` – Léo Coco Jun 01 '17 at 17:21
  • 7
    you could shorten it without an `if` statement with `return typeof o[k] === 'string' && o[k].toLowerCase().includes(string.toLowerCase());` – Nina Scholz Jun 01 '17 at 17:25
  • 4
    If the values are not always strings, then use `String(o[k])` instead of just `o[k]`. See here: https://stackoverflow.com/a/11083415/2110294 – Craig van Tonder Sep 26 '18 at 08:42
  • Also, it would be good to check the type of argument 'string' in the begging, like this function filterByValue(array, string) { return typeof string === 'string' && array.filter(o => Object.keys(o).some(k => { return typeof o[k] === 'string' && o[k].toLowerCase().includes(string.toLowerCase()))); } – Davit Mkrtchyan Apr 02 '20 at 20:06
  • function filterByValue(array, string) { return array.filter(o => Object.keys(o).some(k => o[k].toString().toLowerCase().includes(string.toString().toLowerCase()))); } Slight changes – Sumit Jun 02 '20 at 10:22
  • would be nice if the function could loop through the child objects as well – Jon Harding Dec 23 '20 at 22:36
  • @JonHarding, which child? – Nina Scholz Dec 23 '20 at 22:39
  • @NinaScholz when it checks the o[k] it seems to just move on if it's an object. would be nice to have it check that object – Jon Harding Dec 23 '20 at 22:41
  • @JonHarding, why not implement this yourself? – Nina Scholz Dec 23 '20 at 22:44
  • @NinaScholz so you just wanted to ask which child to patronize? – Jon Harding Dec 23 '20 at 22:45
  • @JonHarding, it is not my request, what do you want? – Nina Scholz Dec 23 '20 at 22:47
21

Well when we already know that its not going to be a search on an object with methods, we can do the following for saving bit on time complexity :

function filterByValue(array, value) {
  return array.filter((data) =>  JSON.stringify(data).toLowerCase().indexOf(value.toLowerCase()) !== -1);
}
binariedMe
  • 4,309
  • 1
  • 18
  • 34
  • This will find the search keyword on the keys of the object too, but may OP just want to search on the values only. – dopeddude Jun 01 '17 at 19:21
  • 4
    yeah, that can be avoided using regex. The idea here was to eliminate the loop over keys of an object – binariedMe Jun 01 '17 at 20:23
  • how to just loop over keys? – chris_r Nov 06 '19 at 04:11
  • @chris_r Once you know that its an hashmap kind of object i.e. no methods, you can always write a regex to do what you want to do. This holds good even for nested objects. – binariedMe Nov 06 '19 at 06:58
  • Example would help. – chris_r Nov 06 '19 at 17:07
  • @chris_r a specific question would help. – binariedMe Nov 15 '19 at 05:34
  • 2
    This was helpful. I also was hoping to find an example here. For anyone else hoping for a nice example here is what I wrote and works for my case. `JSON.stringify(terminal).replace(/("\w+":)/g, '').toLowerCase()`. This turns `"{"myKey":"myValue","myKey2":"myValue2","mySubObject":{"subKey":"subValue"}}"` into `{"myValue","myValue2","subValue"}` – g0rb May 15 '20 at 20:09
  • 1
    This is the best. – Amir Hassan Azimi Jun 09 '20 at 01:29
9

Use Object.keys to loop through the properties of the object. Use reduce and filter to make the code more efficient:

 const results = arrayOfObject.filter((obj)=>{
     return Object.keys(obj).reduce((acc, curr)=>{
           return acc || obj[curr].toLowerCase().includes(term);
     }, false);
}); 

Where term is your search term.

Vincent Ramdhanie
  • 102,349
  • 23
  • 137
  • 192
  • Great! What about highlight search criteria? Can you help me add same tag replacing searched criteria. Something like '' + term + '' – Marcoscdoni Dec 10 '18 at 14:03
  • @Vincent Ramdhanie Is it possible replace searched term without loop through all objects array? – Marcoscdoni Dec 10 '18 at 17:19
5

You can always use array.filter() and then loop through each object and if any of the values match the value you are looking for, return that object.

const arrayOfObject = [{
      name: 'Paul',
      country: 'Canada',
    }, {
      name: 'Lea',
      country: 'Italy',
    }, {
      name: 'John',
      country: 'Italy',
    }, ];
    
let lea = arrayOfObject.filter(function(obj){
  //loop through each object
  for(key in obj){
    //check if object value contains value you are looking for
    if(obj[key].includes('Lea')){
      //add this object to the filtered array
      return obj;
      }
     }
    });
      
console.log(lea);
JJJ
  • 3,314
  • 4
  • 29
  • 43
5

This code checks all the nested values until it finds what it's looking for, then returns true to the "array.filter" for the object it was searching inside(unless it can't find anything - returns false). When true is returned, the object is added to the array that the "array.filter" method returns. When multiple keywords are entered(spaced out by a comma and a space), the search is narrowed further, making it easier for the user to search for something.

Stackblitz example

const data = [
  {
    a: 'aaaaaa',
    b: {
      c: 'c',
      d: {
        e: 'e',
        f: [
          'g',
          {
            i: 'iaaaaaa',
            j: {},
            k: [],
          },
        ],
      },
    },
  },
  {
    a: 'a',
    b: {
      c: 'cccccc',
      d: {
        e: 'e',
        f: [
          'g',
          {
            i: 'icccccc',
            j: {},
            k: [],
          },
        ],
      },
    },
  },
  {
    a: 'a',
    b: {
      c: 'c',
      d: {
        e: 'eeeeee',
        f: [
          'g',
          {
            i: 'ieeeeee',
            j: {},
            k: [],
          },
        ],
      },
    },
  },
];

function filterData(data, filterValues) {
  return data.filter((value) => {
    return filterValues.trim().split(', ').every((filterValue) => checkValue(value, filterValue));
  });
}

function checkValue(value, filterValue) {
  if (typeof value === 'string') {
    return value.toLowerCase().includes(filterValue.toLowerCase());
  } else if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) {
    if (Array.isArray(value)) {
      return value.some((v) => checkValue(v, filterValue));
    } else {
      return Object.values(value).some((v) => checkValue(v, filterValue));
    }
  } else {
    return false;
  }
}

console.log(filterData(data, 'a, c'));
console.log(filterData(data, 'a, c, ic'));
Noob
  • 710
  • 11
  • 15
2

One way would be to use Array#filter, String#toLowerCase and String#indexOf like below.

const arrayOfObject = [{
            name: 'Paul',
            country: 'Canada',
        }, {
            name: 'Lea',
            country: 'Italy',
        }, {
            name: 'John',
            country: 'Italy',
        }];

        function filterByValue(arrayOfObject, term) {
            var ans = arrayOfObject.filter(function(v,i) {
                if(v.name.toLowerCase().indexOf(term) >=0 || v.country.toLowerCase().indexOf(term) >=0) {
                    return true;
                } else false;
            });
            console.log( ans);
        }
        filterByValue(arrayOfObject, 'ita');
Pankaj Shukla
  • 2,657
  • 2
  • 11
  • 18
1

Here is a version of the above which filters by a value which is derived from an array of object's property. The function takes in the array of objects and the specified array of object's property key.

// fake ads list with id and img properties
const ads = [{
  adImg: 'https://test.com/test.png',
  adId: '1'
}, {
  adImg: 'https://test.com/test.png',
  adId: '2'
}, {
  adImg: 'https://test.com/test.png',
  adId: '3'
}, {
  adImg: 'https://test.com/test-2.png',
  adId: '4'
}, {
  adImg: 'https://test.com/test-2.png',
  adId: '5'
}, {
  adImg: 'https://test.com/test-3.png',
  adId: '6'
}, {
  adImg: 'https://test.com/test.png',
  adId: '7'
}, {
  adImg: 'https://test.com/test-6.png',
  adId: '1'
}];

// function takes arr of objects and object property
// convert arr of objects to arr of filter prop values
const filterUniqueItemsByProp = (arrOfObjects, objPropFilter) => {
  return arrOfObjects.filter((item, i, arr) => {
    return arr.map(prop => prop[objPropFilter]).indexOf(item[objPropFilter]) === i;
  });
};

const filteredUniqueItemsByProp = filterUniqueItemsByProp(ads, 'adImg');

console.log(filteredUniqueItemsByProp);
Kevin
  • 31
  • 4
0

function filterByValue(arrayOfObject,words){
  let reg = new RegExp(words,'i');
  return arrayOfObject.filter((item)=>{
     let flag = false;
     for(prop in item){
       if(reg.test(prop)){
          flag = true;
       }  
     }
     return flag;
  });
}
Kermit
  • 1,062
  • 6
  • 10
0

Here's how I would do it using lodash:

const filterByValue = (coll, value) =>
  _.filter(coll, _.flow(
    _.values,
    _.partialRight(_.some, _.method('match', new RegExp(value, 'i')))
  ));

filterByValue(arrayOfObject, 'lea');
filterByValue(arrayOfObject, 'ita');
Adam Boduch
  • 11,023
  • 3
  • 30
  • 38
0

For those who are interested using a for loop solution.

  • Outer loop: Iterate the array.
  • Inner loop: Iterate the object keys of the array[index]
  • Inner loop: Check if typeof array[index][key] === 'string' and the array[index][key] contains the search key -> store the array[index] to the filtered array.
function filterByValue(array, string) {
   let filtered = Array();

   for(let i = 0; i < array.length; i++) { 
      const keys = Object.keys(array[i]);

      for(let j = 0; j < keys.length; j++) { 
         const value = array[i][keys[j]];

         if(typeof value === 'string' &&
            value.toLowerCase().includes(string.toLowerCase())) { 

            filtered.push(array[i]);
            break;
         } else {
            continue;
         }   
      }   
   }
   
   return filtered;
}
const arrayOfObject = [
   {
      name: 'Paul',
      country: 'Canada',
   }, {
      name: 'Lea',
      country: 'Italy',
   }, {
      name: 'John',
      country: 'Italy',
   }, 
];

console.log(filterByValue(arrayOfObject, 'lea')); 
// [{name: 'Lea', country: 'Italy'}]

console.log(filterByValue(arrayOfObject, 'ita')); 
// [{name: 'Lea', country: 'Italy'}, {name: 'John', country: 'Italy'}]
Den Isahac
  • 1,335
  • 11
  • 26