最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to filter array of objects with filter with dynamic properties count? - Stack Overflow

programmeradmin2浏览0评论

I have an array of objects with some bool and int properties:

let employees = [
  { isSkilled: false, isLeader: false, departmentHeadType: 0 },
  { isSkilled: true, isLeader: false, departmentHeadType: 1 },
  { isSkilled: true, isLeader: true, departmentHeadType: 2 }
]

And there is a select where user can check options (in any combinations) by which employees will be filtered:

So after selection I have a filter something like that:

//if user selected "Skilled", "Leaders" and "Heads of departments"
var filter1 = { isSkilled: true, isLeader: true, departmentHeadType: 2 };
//if user selected "Skilled", "Newbie" (so I simply omit isSkilled property in filter
//as both variants are ok), "Leaders", "Heads of departments" and "Deputy heads of departments"
var filter2 = { isLeader: true, departmentHeadType: [1, 2] };

For filter1 I can use the following code:

const filtered = employees.filter(object => JSON.stringify(filter1) == JSON.stringify(object));

But JSON.stringify works only if compared objects have the same properties in the same order. And it will not work for filter2 where isSkilled is omitted and departmentHeadType is array. For filter2 the only idea I have is several ifs and specific properties filtering in each case (that approach is ugly and and not scalable as employee properties grow and thus filter options increase).

Is there a better and more generic aproach for this?

I have an array of objects with some bool and int properties:

let employees = [
  { isSkilled: false, isLeader: false, departmentHeadType: 0 },
  { isSkilled: true, isLeader: false, departmentHeadType: 1 },
  { isSkilled: true, isLeader: true, departmentHeadType: 2 }
]

And there is a select where user can check options (in any combinations) by which employees will be filtered:

So after selection I have a filter something like that:

//if user selected "Skilled", "Leaders" and "Heads of departments"
var filter1 = { isSkilled: true, isLeader: true, departmentHeadType: 2 };
//if user selected "Skilled", "Newbie" (so I simply omit isSkilled property in filter
//as both variants are ok), "Leaders", "Heads of departments" and "Deputy heads of departments"
var filter2 = { isLeader: true, departmentHeadType: [1, 2] };

For filter1 I can use the following code:

const filtered = employees.filter(object => JSON.stringify(filter1) == JSON.stringify(object));

But JSON.stringify works only if compared objects have the same properties in the same order. And it will not work for filter2 where isSkilled is omitted and departmentHeadType is array. For filter2 the only idea I have is several ifs and specific properties filtering in each case (that approach is ugly and and not scalable as employee properties grow and thus filter options increase).

Is there a better and more generic aproach for this?

Share Improve this question edited Mar 7 at 6:33 bairog asked Mar 7 at 6:02 bairogbairog 3,4097 gold badges44 silver badges57 bronze badges 1
  • Why not try to create a function to check if the item value is an array or not before returning the actual filtered data? – Keyboard Corporation Commented Mar 7 at 6:19
Add a comment  | 

3 Answers 3

Reset to default 4

As you noted, JSON.stringify() is not a great option because you cannot reliably guarantee object property order.

You could make it dynamic by iterating the filter object entries (key / value pairs) and creating predicate functions for each, comparing the filter value against the object value for the same key.

You'd need a little extra logic to detect arrays and use an includes check.

const filterRecords = (array, filter) => {
  const predicates = Object.entries(filter).map(
    ([key, val]) =>
      (obj) =>
        Array.isArray(val) ? val.includes(obj[key]) : val === obj[key],
  );

  return array.filter((e) => predicates.every((p) => p(e)));
};

let employees = [
  { isSkilled: false, isLeader: false, departmentHeadType: 0 },
  { isSkilled: true, isLeader: false, departmentHeadType: 1 },
  { isSkilled: true, isLeader: true, departmentHeadType: 2 }
];

//if user selected "Skilled", "Leaders" and "Heads of departments"
var filter1 = { isSkilled: true, isLeader: true, departmentHeadType: 2 };
//if user selected "Skilled", "Newbie" (so I simply omit isSkilled property in filter
//as both variants are ok), "Leaders", "Heads of departments" and "Deputy heads of departments"
var filter2 = { isLeader: true, departmentHeadType: [1, 2] };

console.log('filter1', filterRecords(employees, filter1));
console.log('filter2', filterRecords(employees, filter2));
.as-console-wrapper { max-height: 100% !important; }

Already have the best answer, but i will drop this just in case you need it.

let employees = [
  { isSkilled: false, isLeader: false, departmentHeadType: 0 },
  { isSkilled: true, isLeader: false, departmentHeadType: 1 },
  { isSkilled: true, isLeader: true, departmentHeadType: 2 }
];

function filterFunction(items, filter) {
  return items.filter(item => {
    return Object.keys(filter).every(key => {

      // If the filter value is an array, check if the item value is included in the array. Else check only for equality
      return Array.isArray(filter[key]) ? filter[key].includes(item[key]) : item[key] == filter[key];
    });
  });
}

var filter1 = { isSkilled: true, isLeader: true, departmentHeadType: 2 };
var filter2 = { isLeader: true, departmentHeadType: [1, 2] };

console.log('filtered1', filterFunction(employees, filter1)); // Output: [{ isSkilled: true, isLeader: true, departmentHeadType: 2 }]
console.log('filtered2', filterFunction(employees, filter2)); // Output: [{ isSkilled: true, isLeader: true, departmentHeadType: 2 }]

// Sample data
const arr = [
  { isSkilled: false, isLeader: false, departmentHeadType: 0 },
  { isSkilled: true, isLeader: false, departmentHeadType: 1 },
  { isSkilled: true, isLeader: true, departmentHeadType: 2 }
];

// Filter criteria examples
const filter1 = { isSkilled: true, isLeader: true, departmentHeadType: 2 };
const filter2 = { isLeader: true, departmentHeadType: [1, 2] };

/**
 * Extends Array prototype to support dynamic filtering.
 * @param {Object} filter - An object representing the filtering criteria.
 * @returns {Array} - Filtered array based on the criteria.
 */
Array.prototype.dynamicFilter = function (filter) {
    const filterHash = Object.keys(filter).map(key => {
        return {
            key,
            value: filter[key]
        }
    });
    
    const res = this.filter(obj => {
        let isMatch = true;
        filterHash.forEach(filter => {
            if (Array.isArray(filter.value)) {
                isMatch = filter.value.includes(obj[filter.key]);
            } else if (obj[filter.key] !== filter.value) {
                isMatch = false;
            }
        });
        return isMatch;
    });
    return res;
};

// Test cases
console.log(arr.dynamicFilter(filter1)); 
// Expected output: [{ isSkilled: true, isLeader: true, departmentHeadType: 2 }]
console.log(arr.dynamicFilter(filter2)); 
/* Expected output: [
  { isSkilled: true, isLeader: false, departmentHeadType: 1 },
  { isSkilled: true, isLeader: true, departmentHeadType: 2 }
]*/
发布评论

评论列表(0)

  1. 暂无评论