如何仅在存在多个参数时按多个参数过滤数组
How to filter an array by several params only if they are present
我有一个函数可以通过几个参数过滤结果,例如 name
、status
、type
和日期范围(startDate
和 endDate
).我希望能够通过这些参数一起过滤结果,但前提是它们存在,我。 e.我可以通过 name
和 status
,但不要通过 type
。我不知道如何使用日期范围执行此操作。现在过滤器只有在我传递 startDate
和 endDate
时才起作用,在所有其他情况下,即使存在其他参数并且数组中有相应的数据,它 returns 为空。如何使 startDate
和 endDate
可选?
这是我的过滤器:
if (params.name || params.status || params.type || params.startDate && params.endDate) {
const startDate = new Date(params.startDate).setHours(0,0,0);
const endDate = new Date(params.endDate).setHours(23,59,59);
dataSource = tableListDataSource.filter(
(data) =>
data.name.match(new RegExp(params.name, 'ig')) &&
data.status.includes(params.status || '') &&
data.type.includes(params.type || '') &&
(
new Date(data.createdAt).getTime() > startDate && new Date(data.createdAt).getTime() < endDate
)
);
}
感谢您的帮助!
编辑:
我在后端的一个函数中使用这个过滤器:
function getRule(req, res, u) {
let realUrl = u;
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
realUrl = req.url;
}
const params = parse(realUrl, true).query;
if (params.name || params.status || params.type || params.startDate && params.endDate) {
const startDate = new Date(params.startDate).setHours(0,0,0);
const endDate = new Date(params.endDate).setHours(23,59,59);
dataSource = tableListDataSource.filter(
(data) =>
data.name.match(new RegExp(params.name, 'ig')) &&
data.status.includes(params.status || '') &&
data.type.includes(params.type || '') &&
(
new Date(data.createdAt).getTime() > startDate && new Date(data.createdAt).getTime() < endDate
)
);
}
const result = {
data: dataSource,
success: true,
};
return res.json(result);
}
function getPresentParams(param, checkList) {
// we start with an empty Array
let output = [];
// for each item in checkList
checkList.forEach(item => {
// if the item is an array, multiple params are needed
if (Array.isArray(item)) {
// the item is an itemList
let allItemsPresent = true;
for (const itemListItem of item) {
// if one of the list is not there
if (!param.hasOwnProperty(itemListItem)) {
allItemsPresent = false;
// then stop the loop
break;
}
}
// else all are matching
if (allItemsPresent) {
// add all to our output
output.push(...item);
}
}
// else if the item is not an Array
else {
// simple check if the param is present
if (param.hasOwnProperty(item)) {
output.push(item);
}
}
})
return output;
}
const params = {type: "car", color:"red", tires:4};
// any of "type", "tires" or "windows" should be searched
console.log(getPresentParams(params, ["type", "tires", "windows"]));
// output is ["type", "tires"]
// only show matches with "color" AND "type"
console.log(getPresentParams(params, [["color", "type"]]));
// output is ["color", "type"]
// show matches with "color" or ["windows" AND "tires"]
console.log(getPresentParams(params, ["color", ["windows", "tires"]]));
// output is ["color"]
使用此函数,您将获得一个数组,其中包含您正在搜索的所有当前参数。通过将 2 个或更多 paramNames 作为数组传递,它只会将它们添加到列表中,如果它们都存在的话。
然后您可以简单地检查您的过滤器函数是否存在参数,进行检查,如果失败return false。
const foundParams = getPresentParams(params, ["type", "status", ["startDate", "endDate"], "name"]);
然后在你的过滤函数中:
if (foundParams.includes("startDate") && foundParams.includes("endDate")) {
// if your dates are not matching return false
}
if (foundParams.includes("name")) {
// if your name isn't matching return false
}
// and so on
// at least if all checks have passed
return true;
这只是一种解决方案。还有一些更像是带键的对象:箭头函数和一些迭代。但我认为通过这个解决方案,您可以更好地了解自己在做什么
通过使用您当前的方法,您可以通过执行以下操作使 startDate
和 endDate
成为可选的;
&& (
(params.startDate && params.endDate) ?
(new Date(data.createdAt).getTime() > startDate && new Date(data.createdAt).getTime() < endDate) :
true
)
所以,上面所做的基本上是检查 params.startDate
和 params.endDate
是否没有 falsy values;
- 如果他们不这样做,则使用日期进行现有过滤器;
- 否则,如果其中之一确实具有虚假值,则通过返回
true
来忽略与日期相关的过滤器。
这就是您的最终代码的样子;
if (params.name || params.status || params.type || params.startDate && params.endDate) {
const startDate = new Date(params.startDate).setHours(0,0,0);
const endDate = new Date(params.endDate).setHours(23,59,59);
dataSource = tableListDataSource.filter(
(data) =>
data.name.match(new RegExp(params.name, 'ig')) &&
data.status.includes(params.status || '') &&
data.type.includes(params.type || '') &&
(
(params.startDate && params.endDate) ?
(new Date(data.createdAt).getTime() > startDate && new Date(data.createdAt).getTime() < endDate) :
true
)
);
}
编辑:
一般情况下,我建议不要在FE中进行过滤,而应在BE堆栈中进行。因此,您只能获取所需的数据以及分页支持。
但是,如果您坚持在 FE 中这样做 - 我建议封装过滤功能和处理参数来过滤数据源。
将所有内容列入黑名单,仅将接受的参数列入白名单,并根据需要一路扩展。
以下是我将如何操作的示例。
注意; filterDataSource
复杂性随着您支持过滤的字段数量的增加而增加。其中的字段迭代等于堆叠多个 if 条件和额外的步骤。
/**
* @description Filters dataSource with provided fields
* @param dataSource An array containing the data source
* @param fields An key-value pair containing { [dataSourceField]: callbackFn(value) | "string" | number }
*/
const filterDataSource = (dataSource, fields) => {
if (dataSource && dataSource.length) {
return dataSource.filter((row) => {
const rowFiltered = [];
/**
* @todo Scale the type of filter you want to support and how you want to handle them
*/
for (const fieldName in fields) {
if (Object.hasOwnProperty.call(fields, fieldName) && Object.hasOwnProperty.call(row, fieldName)) {
const filter = fields[fieldName];
if (typeof filter === 'function') {
/** Call the callback function which returns boolean */
rowFiltered.push(!!filter(row));
}
else if (typeof filter === 'object' && filter instanceof RegExp) {
/** Predicate by regex */
rowFiltered.push(!!row[fieldName].match(filter));
}
else if (typeof filter === 'string') {
/** Exact match of string */
rowFiltered.push(!!row[fieldName].match(new RegExp(filter, 'ig')));
}
else if (typeof filter === "number") {
/** Exact match of number */
rowFiltered.push(row[fieldName] === filter);
}
}
}
/** If this row is part of the filter, ONLY return it if all filters passes */
if (rowFiltered.length > 0) {
/** This will check if all filtered return true */
return rowFiltered.every(Boolean);
}
else {
/** If this row is NOT part of the filter, always return it back */
return true;
}
});
}
return dataSource;
}
/**
* @description Filter your datasource with pre-defined filter function for supported params
* @param dataSource An array of object containing the data
* @param params A set of object containing { [param]: value }
* @todo Safely guard the wathched params here, encode them if needed.
*/
const filterDataByParams = (dataSource, params) => {
const fieldsToFilter = {};
if (params.name) {
fieldsToFilter['name'] = new RegExp(params.name, 'ig');
}
if (params.status) {
fieldsToFilter['status'] = params.status;
}
if (params.type) {
fieldsToFilter['type'] = params.type;
}
if (params.startDate && params.endDate) {
/**
* Assuming createdAt is EPOCH
* @todo What is the type of row.createdAt and params.startDate?
* @todo Adjust the logic here and apply validation if needed.
*/
const startMillis = new Date(params.startDate).getTime() / 1e3, // Millis / 1e3 = EPOCH
endMillis = new Date(params.endDate).getTime() / 1e3; // Millis / 1e3 = EPOCH
/** Should we give a nice warning if invalid date value is passed? */
if (isNaN(startMillis) && isNaN(endMillis)) {
console.error('Invalid date params passed. Check it!');
}
/** Random defensive - remove or add more */
if (startMillis && endMillis && startMillis > 0 && endMillis > 0 && startMillis < endMillis) {
fieldsToFilter['createdAt'] = (row) => {
return row.createdAt >= startMillis && row.createdAt <= endMillis;
};
}
}
if (Object.keys(fieldsToFilter).length) {
return filterDataSource(dataSource, fieldsToFilter);
}
else {
return [...dataSource];
}
}
/** 1k Set of mocked data source with createdAt between 1 Jan 2019 to 13 February 2021 */
fetch('https://api.jsonbin.io/b/6027ee0987173a3d2f5c9c3d/3').then((resp) => {
return resp.json();
}).then((mockDataSource) => {
mazdaFilteredData = filterDataByParams(mockDataSource, {
'name': 'Mazda',
'startDate': '2019-05-04T19:06:20Z',
'endDate': '2020-08-09T19:06:20Z'
});
hondaFilteredData = filterDataByParams(mockDataSource, {
'name': 'honda',
'startDate': '2019-10-05T00:00:00Z',
'endDate': '2020-12-09T23:23:59Z'
});
mercedezFilteredData = filterDataByParams(mockDataSource, {
'name': 'merce',
'startDate': '2020-01-01T00:00:00Z',
'endDate': '2021-12-31T23:23:59Z'
})
console.log({mazdaFilteredData, hondaFilteredData, mercedezFilteredData});
});
我有一个函数可以通过几个参数过滤结果,例如 name
、status
、type
和日期范围(startDate
和 endDate
).我希望能够通过这些参数一起过滤结果,但前提是它们存在,我。 e.我可以通过 name
和 status
,但不要通过 type
。我不知道如何使用日期范围执行此操作。现在过滤器只有在我传递 startDate
和 endDate
时才起作用,在所有其他情况下,即使存在其他参数并且数组中有相应的数据,它 returns 为空。如何使 startDate
和 endDate
可选?
这是我的过滤器:
if (params.name || params.status || params.type || params.startDate && params.endDate) {
const startDate = new Date(params.startDate).setHours(0,0,0);
const endDate = new Date(params.endDate).setHours(23,59,59);
dataSource = tableListDataSource.filter(
(data) =>
data.name.match(new RegExp(params.name, 'ig')) &&
data.status.includes(params.status || '') &&
data.type.includes(params.type || '') &&
(
new Date(data.createdAt).getTime() > startDate && new Date(data.createdAt).getTime() < endDate
)
);
}
感谢您的帮助!
编辑:
我在后端的一个函数中使用这个过滤器:
function getRule(req, res, u) {
let realUrl = u;
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
realUrl = req.url;
}
const params = parse(realUrl, true).query;
if (params.name || params.status || params.type || params.startDate && params.endDate) {
const startDate = new Date(params.startDate).setHours(0,0,0);
const endDate = new Date(params.endDate).setHours(23,59,59);
dataSource = tableListDataSource.filter(
(data) =>
data.name.match(new RegExp(params.name, 'ig')) &&
data.status.includes(params.status || '') &&
data.type.includes(params.type || '') &&
(
new Date(data.createdAt).getTime() > startDate && new Date(data.createdAt).getTime() < endDate
)
);
}
const result = {
data: dataSource,
success: true,
};
return res.json(result);
}
function getPresentParams(param, checkList) {
// we start with an empty Array
let output = [];
// for each item in checkList
checkList.forEach(item => {
// if the item is an array, multiple params are needed
if (Array.isArray(item)) {
// the item is an itemList
let allItemsPresent = true;
for (const itemListItem of item) {
// if one of the list is not there
if (!param.hasOwnProperty(itemListItem)) {
allItemsPresent = false;
// then stop the loop
break;
}
}
// else all are matching
if (allItemsPresent) {
// add all to our output
output.push(...item);
}
}
// else if the item is not an Array
else {
// simple check if the param is present
if (param.hasOwnProperty(item)) {
output.push(item);
}
}
})
return output;
}
const params = {type: "car", color:"red", tires:4};
// any of "type", "tires" or "windows" should be searched
console.log(getPresentParams(params, ["type", "tires", "windows"]));
// output is ["type", "tires"]
// only show matches with "color" AND "type"
console.log(getPresentParams(params, [["color", "type"]]));
// output is ["color", "type"]
// show matches with "color" or ["windows" AND "tires"]
console.log(getPresentParams(params, ["color", ["windows", "tires"]]));
// output is ["color"]
使用此函数,您将获得一个数组,其中包含您正在搜索的所有当前参数。通过将 2 个或更多 paramNames 作为数组传递,它只会将它们添加到列表中,如果它们都存在的话。
然后您可以简单地检查您的过滤器函数是否存在参数,进行检查,如果失败return false。
const foundParams = getPresentParams(params, ["type", "status", ["startDate", "endDate"], "name"]);
然后在你的过滤函数中:
if (foundParams.includes("startDate") && foundParams.includes("endDate")) {
// if your dates are not matching return false
}
if (foundParams.includes("name")) {
// if your name isn't matching return false
}
// and so on
// at least if all checks have passed
return true;
这只是一种解决方案。还有一些更像是带键的对象:箭头函数和一些迭代。但我认为通过这个解决方案,您可以更好地了解自己在做什么
通过使用您当前的方法,您可以通过执行以下操作使 startDate
和 endDate
成为可选的;
&& (
(params.startDate && params.endDate) ?
(new Date(data.createdAt).getTime() > startDate && new Date(data.createdAt).getTime() < endDate) :
true
)
所以,上面所做的基本上是检查 params.startDate
和 params.endDate
是否没有 falsy values;
- 如果他们不这样做,则使用日期进行现有过滤器;
- 否则,如果其中之一确实具有虚假值,则通过返回
true
来忽略与日期相关的过滤器。
这就是您的最终代码的样子;
if (params.name || params.status || params.type || params.startDate && params.endDate) {
const startDate = new Date(params.startDate).setHours(0,0,0);
const endDate = new Date(params.endDate).setHours(23,59,59);
dataSource = tableListDataSource.filter(
(data) =>
data.name.match(new RegExp(params.name, 'ig')) &&
data.status.includes(params.status || '') &&
data.type.includes(params.type || '') &&
(
(params.startDate && params.endDate) ?
(new Date(data.createdAt).getTime() > startDate && new Date(data.createdAt).getTime() < endDate) :
true
)
);
}
编辑:
一般情况下,我建议不要在FE中进行过滤,而应在BE堆栈中进行。因此,您只能获取所需的数据以及分页支持。
但是,如果您坚持在 FE 中这样做 - 我建议封装过滤功能和处理参数来过滤数据源。 将所有内容列入黑名单,仅将接受的参数列入白名单,并根据需要一路扩展。
以下是我将如何操作的示例。
注意; filterDataSource
复杂性随着您支持过滤的字段数量的增加而增加。其中的字段迭代等于堆叠多个 if 条件和额外的步骤。
/**
* @description Filters dataSource with provided fields
* @param dataSource An array containing the data source
* @param fields An key-value pair containing { [dataSourceField]: callbackFn(value) | "string" | number }
*/
const filterDataSource = (dataSource, fields) => {
if (dataSource && dataSource.length) {
return dataSource.filter((row) => {
const rowFiltered = [];
/**
* @todo Scale the type of filter you want to support and how you want to handle them
*/
for (const fieldName in fields) {
if (Object.hasOwnProperty.call(fields, fieldName) && Object.hasOwnProperty.call(row, fieldName)) {
const filter = fields[fieldName];
if (typeof filter === 'function') {
/** Call the callback function which returns boolean */
rowFiltered.push(!!filter(row));
}
else if (typeof filter === 'object' && filter instanceof RegExp) {
/** Predicate by regex */
rowFiltered.push(!!row[fieldName].match(filter));
}
else if (typeof filter === 'string') {
/** Exact match of string */
rowFiltered.push(!!row[fieldName].match(new RegExp(filter, 'ig')));
}
else if (typeof filter === "number") {
/** Exact match of number */
rowFiltered.push(row[fieldName] === filter);
}
}
}
/** If this row is part of the filter, ONLY return it if all filters passes */
if (rowFiltered.length > 0) {
/** This will check if all filtered return true */
return rowFiltered.every(Boolean);
}
else {
/** If this row is NOT part of the filter, always return it back */
return true;
}
});
}
return dataSource;
}
/**
* @description Filter your datasource with pre-defined filter function for supported params
* @param dataSource An array of object containing the data
* @param params A set of object containing { [param]: value }
* @todo Safely guard the wathched params here, encode them if needed.
*/
const filterDataByParams = (dataSource, params) => {
const fieldsToFilter = {};
if (params.name) {
fieldsToFilter['name'] = new RegExp(params.name, 'ig');
}
if (params.status) {
fieldsToFilter['status'] = params.status;
}
if (params.type) {
fieldsToFilter['type'] = params.type;
}
if (params.startDate && params.endDate) {
/**
* Assuming createdAt is EPOCH
* @todo What is the type of row.createdAt and params.startDate?
* @todo Adjust the logic here and apply validation if needed.
*/
const startMillis = new Date(params.startDate).getTime() / 1e3, // Millis / 1e3 = EPOCH
endMillis = new Date(params.endDate).getTime() / 1e3; // Millis / 1e3 = EPOCH
/** Should we give a nice warning if invalid date value is passed? */
if (isNaN(startMillis) && isNaN(endMillis)) {
console.error('Invalid date params passed. Check it!');
}
/** Random defensive - remove or add more */
if (startMillis && endMillis && startMillis > 0 && endMillis > 0 && startMillis < endMillis) {
fieldsToFilter['createdAt'] = (row) => {
return row.createdAt >= startMillis && row.createdAt <= endMillis;
};
}
}
if (Object.keys(fieldsToFilter).length) {
return filterDataSource(dataSource, fieldsToFilter);
}
else {
return [...dataSource];
}
}
/** 1k Set of mocked data source with createdAt between 1 Jan 2019 to 13 February 2021 */
fetch('https://api.jsonbin.io/b/6027ee0987173a3d2f5c9c3d/3').then((resp) => {
return resp.json();
}).then((mockDataSource) => {
mazdaFilteredData = filterDataByParams(mockDataSource, {
'name': 'Mazda',
'startDate': '2019-05-04T19:06:20Z',
'endDate': '2020-08-09T19:06:20Z'
});
hondaFilteredData = filterDataByParams(mockDataSource, {
'name': 'honda',
'startDate': '2019-10-05T00:00:00Z',
'endDate': '2020-12-09T23:23:59Z'
});
mercedezFilteredData = filterDataByParams(mockDataSource, {
'name': 'merce',
'startDate': '2020-01-01T00:00:00Z',
'endDate': '2021-12-31T23:23:59Z'
})
console.log({mazdaFilteredData, hondaFilteredData, mercedezFilteredData});
});