【问题标题】:How to filter an array by several params only if they are present仅当它们存在时如何通过多个参数过滤数组
【发布时间】:2021-05-12 12:49:21
【问题描述】:

我有一个按多个参数过滤结果的功能,例如namestatustype 和日期范围(startDateendDate)。我希望能够通过这些参数一起过滤结果,但前提是它们存在,即。 e.我可以通过namestatus,但不要通过type。我不知道如何在日期范围内做到这一点。现在过滤器只有在我通过startDateendDate时才起作用,在所有其他情况下,即使存在其他参数并且数组中有相应的数据,它也会返回null。如何将startDateendDate 设为可选?

这是我的过滤器:

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);
}

【问题讨论】:

  • 1) ||将比较值转换为布尔值。因此,即使您将值设置为 0,您对该值的检查也会失败。采用 ??相反或 params.hasOwnProperty("type"), ... 2) 这是对象销毁中预定义参数的完美案例。您的函数可能看起来像 function ({name = null, type = null, status = null} = {}) {...} 只需为它编写一个程序。在每个非空选项的 filter() 内部进行检查。如果所有检查都通过返回 true,否则在第一次失败时返回 false。
  • @KaiLehmann 非常感谢您的回复。您能否举一个此类功能的示例并将其发布为答案?
  • @KaiLehmann 参数在这样的 url 中传递:/api/order?startDate=2021-02-09&amp;endDate=2021-03-15,所以它不是一个对象
  • 这能回答你的问题吗? Filter collection with optional values
  • @KaiLehmann 非常感谢,我去看看!

标签: javascript filter filtering


【解决方案1】:
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 作为 Array 传递,它只会在它们都存在时将它们添加到列表中。

然后,如果参数存在,您可以简单地检查您的过滤器函数,进行检查,如果失败则返回 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;

这只是一种解决方案。还有一些更像是一个带键的对象:箭头函数和一些迭代。但我认为通过这个解决方案,您可以更好地了解自己在做什么

【讨论】:

  • 感谢您的回复,但我在这里运行getPresentParams 函数时得到params.hasOwnProperty is not a function // simple check if the param is present
  • 哦,打错字了。它应该是参数而不是参数。我会改正的
  • @jupiteror 顺便说一句,感谢您要求提供代码,然后提供非故障保存代码 thumbsup
  • 我修正了错字,但您的代码仍然无法正常工作。我是编程新手,所以我无法仅从 cmets 得到一个想法,这就是我要求提供代码示例的原因。
  • 所以问题不在于你想学习如何解决问题。问题是您希望在不了解发生了什么的情况下复制和粘贴代码。
【解决方案2】:

通过使用您当前的方法,您可以通过以下操作将startDateendDate 设为可选;

&& (
  (params.startDate && params.endDate) ? 
    (new Date(data.createdAt).getTime() > startDate && new Date(data.createdAt).getTime() < endDate) :
    true
)

所以,上面所做的基本上是检查params.startDateparams.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 复杂性随着您支持过滤的字段数量而增加。里面的fields迭代,就是多步叠加多个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});
});

【讨论】:

  • params.startDate === undefined || params.endDate === undefined || new Date() ...相同。
  • @MarcoLuzzara 不是,falsies 不仅仅是undefined。我知道你的意思,但你仍然需要返回 truthy 值才能忽略日期过滤器。在您上面的代码中,它会向.filter 方法返回一个falsy - 其中tableListDataSource 将是一个空数组,因为过滤器将全部返回falsy
  • 好吧,我明白你的意思了。如果没有提供,我只是假设他们是undefined
  • 这正是我想要的!非常感谢您的回答和解释! ?
  • @choz 非常感谢您的更新!实际上,我在后端使用这个过滤器,我已经更新了我的问题以显示整个功能。我正在尝试合并您的代码,但出现语法错误。我会再尝试一些。无论如何,非常感谢您的帮助!
猜你喜欢
  • 2011-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-23
  • 2018-06-15
  • 1970-01-01
  • 1970-01-01
  • 2019-08-09
相关资源
最近更新 更多