网站搜索

javascript - 如何根据对象过滤对象数组?


我有一个对象数组:

let response = [{"id": 1, "name": "Alise", "price": 400, "category": 4}];

另外,我还有一些用于过滤的数组:

let names = ["Jessy", "Megan"];
let prices = [300, 500];
let category = [1,2,4];

我需要根据这些数组来过滤数组,条件是在 names, prices, category 之间使用 AND,并且在每个数组的元素之间使用 OR 条件:Jessy OR Megan 等等

我做了这个解决方案:

const filterByCategory = (response: IResponse[], filterBy: any[]) => response.filter((e: IResponse) => filterBy.indexOf(e.category) > -1);
const filterByPrice =  (response: IResponse[], filterBy: any[]) => response.filter((e: IResponse) => e.price in filterBy);

然后我不知道如何让通话更好:

一种方式:

filter() {
  let filtered = filterByCategory(response, category);
  filtered = filterByPrice(filtered, prices);
}

第二:

filter() {
   let filtered = [];
   if (category && category.length) {
      filtered = filterByCategory(response, category);
   }

   if (prices && prices.length) {
       filtered = filterByCategory(filtered, category);
   }

}

第三:

let filtered = response.filter((element) => {
    return category && category.indexOf(e.category) > -1 && 
    prices && prices.indexOf(e.price) > -1 && etc.
});
  • 首先是简单修改过滤逻辑,而不更改主过滤器
  • 第二个和第一个一样,尽管进行了额外检查
  • 第三种方式是最短的,但将来难以修改

答案#1

像这样的东西是清晰、简洁且易于维护的。

const filteredItems = response.filter(e => {
    const validations = {
       containsCategory: category && category.includes(e.category),
       containsPrice: prices && prices.includes(e.price),
       containsName: names && names.includes(e.name),
    };

    return Object.values(validations).every(v => v);
});

你可以在这里使用一个对象,这样你不仅可以获得为验证命名的好处,还可以迭代它们并检查每个 validation === true。如果你把每个验证定义为变量,那么你将有重复的代码。例如

const filteredItems = response.filter(e => {
   const containsCategory = category && category.includes(e.category);
   const containsPrice = prices && prices.includes(e.price);
   const containsName = names && names.includes(e.name);

   return containsName && containsPrice && containsName;
});

每次你想添加一个验证时,你必须在两个地方更新名称(返回值和定义)。

重要的是要注意,虽然使用你发布的代码可以用更简洁的方式实现相同的结果,但这种方法可以让你在未来轻松添加更多的验证类别——而更简洁的解决方案则做不到这一点。

例如,你也可以轻松地向 validations 添加字符串过滤器或高级过滤功能:

const validations = {
   matchesSearchString: !searchString || e.name.toLowerCase().startsWith(searchSting.toLowerCase()),
   isAnAncientQueen: isAnAncientQueen(e.name),
   /* -- snip -- */
};

答案#2

我会说其他答案更简洁,但如果你能够利用 TypeScript 的类型系统来做出一个安全且非常通用的解决方案,这也是可行的。是否采用这种方法取决于你的具体需求,尤其是关于类型安全和可维护性方面。

我的通用方法是定义一种可以应用于任何对象的过滤器类型,然后实现一个能够将过滤器应用于任意类型数组的函数。

对于“filter”,这将有效:

type Filter<T> = {
    [P in keyof T]?: (T[P])[]
}

你可以将其理解为“对于 T 类型的每个成员 X,Filter<T> 将有一个可选的同名数组,类型为 X[]。”

然后过滤函数变为:

function filter<T>(items: T[], filter: Filter<T>): T[]{
    // interim result for easier debugging
    let result =
        items.filter(
            item => {
                // check each member in the filter
                for (let key in filter) {
                    let valueInFilter = filter[key];
                    // if it's empty, then it's OK
                    if (valueInFilter) {
                        // otherwise the value on item MUST be found in the array on filter
                        let valueInItem = item[key];
                        if (valueInFilter.indexOf(valueInItem) == -1) {
                            return false;
                        }
                    }
                }

                // if every check passes, keep the item
                return true;
            }
        );

    return result;
}

把所有的东西放在一起,看起来就像这样:

let responses: IResponse[] = [
    { "id": 1, "name": "Alise", "price": 400, "category": 4 },
    { "id": 2, "name": "Bob", "price": 300, "category": 2 }
];

let someFilter: Filter<IResponse> = {
    id: [1, 2, 4],

    price: [300]
};

console.log(filter(responses, someFilter))

我在 TypeScript Playground 上整理了这个,所以你可以看到所有的类型检查都可以正常工作。