0

Lets assume I have an array of objects:

let users = [{
 name: "Mark",
 location: "US",
 job: "engineer"
},
{
 name: "Mark",
 location: "US",
 job: "clerk"
},
{
 name: "Angela",
 location: "Europe",
 job: "pilot"
}, 
{
 name: "Matthew",
 location: "US",
 job: "engineer"
}]

and I have a filter object with all categories I want to filter data against (there can be multiple values per key):

const filters = {
  name: ["Mark", "Matthew"],
  location: ["US"],
  job: ["Engineer"]
}

Based on these filters and data the expected result would return:

[{name: "Mark", location: "US", job: "Engineer"}, {name: "Matthew", location: "US", job: "Engineer"}]

I have tried filtering with:

users.filter(user => {
  for(let k in filters) {
    if(user[k] === filters[k]) {
      return true;
    }
  }
})

however, this method doesn't take into account that a filter category might contain more than one value which I can handle by doing like:

filters[k][0] or filters[k][1]

but it isn't dynamic.

If anyone has any input that would be much appreciated! Thank you.

fev3r
  • 79
  • 6

2 Answers2

2

Use Object.entries() on filters to get an array of [key, values] pairs. Iterate the pairs with Array.every(), and check that each pair includes the value of the current object.

const fn = (arr, filters) => {
  const filtersArr = Object.entries(filters)
  
  return arr.filter(o => 
    filtersArr.every(([key, values]) => 
      values.includes(o[key])
    )
  )
}

const users = [{"name":"Mark","location":"US","job":"engineer"},{"name":"Mark","location":"US","job":"clerk"},{"name":"Angela","location":"Europe","job":"pilot"},{"name":"Matthew","location":"US","job":"engineer"}]

const filters = {
  name: ["Mark", "Matthew"],
  location: ["US"],
  job: ["engineer"]
}

const result = fn(users, filters)

console.log(result)

One caveat of using Array.includes() is that differences in case would provide a false answer (Engineer and engineer in this case). To solve that convert the current word to a RegExp, with the Case-insensitive search flag (i), and check using Array.some() if it fits any of the words in the array.

const fn = (arr, filters) => {
  const filtersArr = Object.entries(filters)
  
  return arr.filter(o => 
    filtersArr.every(([key, values]) => {
      const pattern = new RegExp(`^${o[key]}$`, 'i')
    
      return values.some(v => pattern.test(v))
    })    
  )
}

const users = [{"name":"Mark","location":"US","job":"engineer"},{"name":"Mark","location":"US","job":"clerk"},{"name":"Angela","location":"Europe","job":"pilot"},{"name":"Matthew","location":"US","job":"engineer"}]

const filters = {
  name: ["Mark", "Matthew"],
  location: ["US"],
  job: ["Engineer"]
}

const result = fn(users, filters)

console.log(result)
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
1

You can loop over the entries of the filters object and ensure that the value of each key is one of the allowed ones.

let users = [{name:"Mark",location:"US",job:"Engineer"},{name:"Mark",location:"US",job:"clerk"},{name:"Angela",location:"Europe",job:"pilot"},{name:"Matthew",location:"US",job:"Engineer"}];
const filters = {
  name: ["Mark", "Matthew"],
  location: ["US"],
  job: ["Engineer"]
};
const res = users.filter(o => 
  Object.entries(filters).every(([k,v])=>v.includes(o[k])));
console.log(res);
Unmitigated
  • 76,500
  • 11
  • 62
  • 80