L. Fath
L. Fath

Reputation: 65

JavaScript filter array with filters of another array

I'm currently trying to filter an array by multiple filters stored in another array. A filter-object contains the name of the attribute we want to filter, the operator and the condition. So it could look like this:

let filters = [{
  attribute: 'id',
  operator: '<',
  condition: 5
}, {
  attribute: 'name',
  operator: '=',
  condition: 'Max'
}]

And the Array with the item could look like this:

const items = [{
  id: 0,
  name: 'Max',
  nachname: 'Maier'
}, {
  id: 1,
  name: 'Peter',
  nachname: 'Huber'
}, {
  id: 2,
  name: 'Max',
  nachname: 'Brooks'
}, {
  id: 3,
  name: 'Sara',
  nachname: 'Scott'
}, {
  id: 4,
  name: 'Peter',
  nachname: 'Evans'
}]

Can you please help me out?

My current solutions (see below) always returns all elements. So it doesn't filter anything.

items.filter((item) => {
    filters.forEach((filter) => {
      switch (filter.operator) {
        case '<':
          if (!(item[filter.attribute] < filter.condition)) return false
          break
        case '<=':
          if (!(item[filter.attribute] <= filter.condition)) return false
          break
        case '=':
          if (!(item[filter.attribute] === filter.condition)) return false
          break
        case '>':
          if (!(item[filter.attribute] > filter.condition)) return false
          break
        case '>=':
          if (!(item[filter.attribute] >= filter.condition)) return false
          break
        case '*wildcard*':
          if (!item[filter.attribute].includes(filter.condition))
            return false
          break
      }
    })
    return true
  })

Thanks in advance! :)

Upvotes: 1

Views: 73

Answers (2)

Phil
Phil

Reputation: 164731

Your main problem is that forEach callback function return values are not used.

Sounds like what you'd want instead is to use Array.prototype.every() on your filters array for every item in items

const filters = [{"attribute":"id","operator":"<","condition":5},{"attribute":"name","operator":"=","condition":"Max"}]

const items = [{"id":0,"name":"Max","nachname":"Maier"},{"id":1,"name":"Peter","nachname":"Huber"},{"id":2,"name":"Max","nachname":"Brooks"},{"id":3,"name":"Sara","nachname":"Scott"},{"id":4,"name":"Peter","nachname":"Evans"}]

const filtered = items.filter(item =>
  filters.every(({ attribute, operator, condition }) => {
    switch (operator) {
      case "<":
        return item[attribute] < condition
      case ">":
        return item[attribute] > condition
      case "=":
        return item[attribute] == condition
      // and so on
    }
    return true
  })
)

console.info(filtered)
.as-console-wrapper { max-height: 100% !important }

Upvotes: 1

Adam Jenkins
Adam Jenkins

Reputation: 55613

You need to return a boolean from the filter callback based on the filters. Right now you are just always returning true from the callback function passed to filter.

Try something like this:

const doesItemPassFilter = item => filter => {
  // your filter logic
}

items.filter(item => filters.every(doesItemPassFilter(item)));

I've based my concept that your filters are "and"'s - meaning that an item has to match them all. If your filters are "or"'s - meaning that an item has to match at least one filter - then just change every to some.

Upvotes: 2

Related Questions