0

I can't seem to think about how I can overcome this issue where there might be any amount of filters as objects which will help me to filter out the data array.

  data = [
    {
      id: 1,
      first_name: 'Colver',
    }, {
      id: 2,
      first_name: 'Brodie',
    }, {
      id: 3,
      first_name: 'Philippa',
    }, {
      id: 4,
      first_name: 'Taite',
    }, {
      id: 5,
      first_name: 'Pierson'
    }
  ];

  filters = [
    {
      field: 'id',
      operator: 'between',
      value: '2-5'
    },
    {
      field: 'first_name',
      operator: 'eq',
      value: 'Philippa'
    }
  ];

  ngOnInit(): void {
    const filteredItems = [];
    this.data.forEach(item => {
      this.filters.forEach((filter, filterIndex) => {
        const itemValue = item[filter.field];
        switch (filter.operator) {
          case 'eq':
            if (itemValue === filter.value) {
              filteredItems.push(item);
            }
            break;
          case 'between':
            const [firstValue, secondValue] = filter.value.split('-');
            if (itemValue > firstValue && itemValue < secondValue) {
              filteredItems.push(item);
            }
            break;
        }
      });
    });
    console.log(filteredItems);
  }

I basically want the filteredItems to output like below since the id is between 2 and 5 and the first_name is Philippa. But since I'm iterating the filters 2 times both the times items gets pushed to filteredItems.

 [{
      id: 3,
      first_name: 'Philippa',
  }]
6
  • Are 'eq' and 'between' the only two possible values? Commented Dec 11, 2020 at 19:40
  • No there are way more, this is to simplify the question. Commented Dec 11, 2020 at 19:41
  • Are the filters conjunctive or disjunctive, i. e. does an item have to pass one or all to be part of the resulting array? Commented Dec 11, 2020 at 19:46
  • It should be an intersection of all the filters. Not just one. Commented Dec 11, 2020 at 19:48
  • Are you open to other ways of specifying the filters? Commented Dec 11, 2020 at 20:33

4 Answers 4

3

You could take Array#every and an object for getting the right operator function.

const
    data = [{ id: 1, first_name: 'Colver' }, { id: 2, first_name: 'Brodie' }, { id: 3, first_name: 'Philippa' }, { id: 4, first_name: 'Taite' }, { id: 5, first_name: 'Pierson' }],
    filters = [{ field: 'id', operator: 'between', value: '2-5' }, { field: 'first_name', operator: 'eq', value: 'Philippa' }],
    operators = {
        between: (field, range) => {
            const [min, max] = range.split('-').map(Number);
            return min <= field && field <= max;
        },
        eq: (field, value) => field === value
    },
    result = data.filter(o =>
        filters.every(({ field, operator, value }) =>
            operators[operator](o[field], value)
        )
    );
  
console.log(result);

Sign up to request clarification or add additional context in comments.

1 Comment

thanks, this is really great code. I'll try to expand on this method.
0

You can perform a reduce operation over the filters array and use Array#filter to remove objects on each iteration.

const data = [
    {
      id: 1,
      first_name: 'Colver',
    }, {
      id: 2,
      first_name: 'Brodie',
    }, {
      id: 3,
      first_name: 'Philippa',
    }, {
      id: 4,
      first_name: 'Taite',
    }, {
      id: 5,
      first_name: 'Pierson'
    }
  ],
  filters = [
    {
      field: 'id',
      operator: 'between',
      value: '2-5'
    },
    {
      field: 'first_name',
      operator: 'eq',
      value: 'Philippa'
    }
  ];
const res = filters.reduce((acc,{field,operator,value})=>
   acc.filter(o => operator === 'eq' && o[field] === value || 
   operator === 'between' && o[field] >= value.split('-')[0] 
   && o[field] <= value.split('-')[1]), data);
console.log(res);

1 Comment

Consider clicking the Tidy button when editing your snippet. That reduce is quite hard to parse.
0

Use Array.prototype.every to make sure every filter passes, and if so, push it to the array:

ngOnInit(): void {
  const filteredItems = this.data.forEach(item => 
    this.filters.every((filter, filterIndex) => {
      const itemValue = item[filter.field];
      switch (filter.operator) {
        case 'eq':
          if (itemValue === filter.value) {
            return true;
          }
          break;
        case 'between':
          const [firstValue, secondValue] = filter.value.split('-');
          if (itemValue > firstValue && itemValue < secondValue) {
            return true;
          }
          break;
      }
      return false;
    })
  );
  console.log(filteredItems);
}

1 Comment

returned value is undefined
0

Instead of using

const filteredItems = [];
this.data.forEach(item => {
  // [...]
  filteresItems.push(item)
  // [...]
});

use Array's filter:

const filteredItems = this.data.filter(item => {
  // [...]
  let match = true; // or false
  return match;
});

Taking your whole example, you could use:

function passes(item, filter) {
  const itemValue = item[filter.field];
  switch (filter.operator) {
    case 'eq':
      if (itemValue === filter.value) {
        return true;
      }
    case 'between':
      const [firstValue, secondValue] = filter.value.split('-');
      if (itemValue > firstValue && itemValue < secondValue) {
        return true;
      }
  }
  return false;
}

const filteredItems = this.data.filter(
  item => this.filters
    .map(filter => passes(item, filter))
    .every());

Comments

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.