返回

如何在 JavaScript 中使用多个键值对过滤嵌套 JSON?

javascript

如何使用多个键值对过滤嵌套 JSON 数据

在 JavaScript 中,处理嵌套 JSON 数据时,我们常常需要根据特定的键值对进行筛选。这种需求在实际开发中十分常见,例如,你可能需要从一个包含用户信息的 JSON 数组中,筛选出居住在某个城市且年龄在特定范围内的用户。

直接使用 filter 方法可以过滤数组元素,但无法直接应用于嵌套 JSON 数据的多键值对过滤。本文将介绍一种通用的解决方案,帮助你高效地使用多个键值对过滤嵌套 JSON 数据。

深入分析

假设我们有一个包含用户信息的 JSON 数组:

[
  {
    "name": "Alice",
    "age": 30,
    "address": {
      "city": "New York",
      "zip": "10001"
    }
  },
  {
    "name": "Bob",
    "age": 25,
    "address": {
      "city": "Los Angeles",
      "zip": "90001"
    }
  },
  {
    "name": "Charlie",
    "age": 35,
    "address": {
      "city": "New York",
      "zip": "10010"
    }
  }
]

我们希望筛选出居住在 "New York" 且年龄大于 30 岁的用户。在这种情况下,我们需要检查两个键值对:address.cityage

解决方案:递归过滤

为了解决这个问题,我们可以创建一个递归函数,该函数接受三个参数:

  1. data:要过滤的 JSON 数据。
  2. filters:一个包含过滤条件的对象,例如 { "address.city": "New York", "age": { "$gt": 30 } }
  3. parentKey:当前处理的键的父键(可选,用于递归调用)。

该函数的工作原理如下:

  1. 遍历 filters 对象中的每个键值对。
  2. 如果键名包含 ".",则使用递归调用处理嵌套对象。
  3. 否则,根据条件判断是否保留当前数据。

代码实现

function filterNestedJSON(data, filters, parentKey = '') {
  if (Array.isArray(data)) {
    return data.filter(item => filterNestedJSON(item, filters));
  } else if (typeof data === 'object' && data !== null) {
    for (const [key, value] of Object.entries(filters)) {
      const currentKey = parentKey ? `${parentKey}.${key}` : key;
      if (currentKey.includes('.')) {
        const [nestedKey, ...restKeys] = currentKey.split('.');
        if (data.hasOwnProperty(nestedKey)) {
          const nestedFilters = { [restKeys.join('.')]: value };
          if (!filterNestedJSON(data[nestedKey], nestedFilters, nestedKey)) {
            return false;
          }
        } else {
          return false;
        }
      } else if (typeof value === 'object') {
        for (const [operator, operand] of Object.entries(value)) {
          switch (operator) {
            case '$gt':
              if (!(data[currentKey] > operand)) return false;
              break;
            case '$lt':
              if (!(data[currentKey] < operand)) return false;
              break;
            case '$gte':
              if (!(data[currentKey] >= operand)) return false;
              break;
            case '$lte':
              if (!(data[currentKey] <= operand)) return false;
              break;
            case '$eq':
              if (!(data[currentKey] === operand)) return false;
              break;
            case '$ne':
              if (!(data[currentKey] !== operand)) return false;
              break;
            default:
              throw new Error(`Unsupported operator: ${operator}`);
          }
        }
      } else if (data[currentKey] !== value) {
        return false;
      }
    }
    return true;
  } else {
    return false;
  }
}

const data = [
  {
    "name": "Alice",
    "age": 30,
    "address": {
      "city": "New York",
      "zip": "10001"
    }
  },
  {
    "name": "Bob",
    "age": 25,
    "address": {
      "city": "Los Angeles",
      "zip": "90001"
    }
  },
  {
    "name": "Charlie",
    "age": 35,
    "address": {
      "city": "New York",
      "zip": "10010"
    }
  }
];

const filters = { "address.city": "New York", "age": { "$gt": 30 } };

const filteredData = data.filter(item => filterNestedJSON(item, filters));

console.log(filteredData); // Output: [{ name: 'Charlie', age: 35, address: { city: 'New York', zip: '10010' } }]

代码解读

  1. filterNestedJSON 函数首先判断数据类型:
    • 如果是数组,则递归调用自身过滤每个元素。
    • 如果是对象,则遍历 filters 对象进行过滤。
    • 否则,返回 false
  2. 对于每个键值对,判断键名是否包含 ".",如果是则进行递归调用处理嵌套对象。
  3. 如果键值对的值是对象,则遍历该对象,根据不同的操作符进行比较。
  4. 如果所有条件都满足,则返回 true,否则返回 false

总结

通过递归函数和灵活的条件判断,我们可以轻松地使用多个键值对过滤嵌套 JSON 数据。这种方法可以处理任意深度的嵌套结构,并且支持各种比较操作符,例如 $gt$lt$eq 等,极大地提高了代码的灵活性和可复用性。

常见问题解答

  1. 问:如果我想使用其他比较操作符,例如 "包含" 或 "不包含",该如何修改代码?

    答: 你可以在 filterNestedJSON 函数中添加相应的 if 语句来处理其他操作符。例如,要检查某个字段是否包含某个字符串,可以使用 includes 方法:

    if (operator === '$in' && !data[currentKey].includes(operand)) {
      return false;
    }
    
  2. 问:如何处理数组类型的字段?

    答: 你可以使用 someevery 方法来检查数组中的元素是否满足条件。例如,要检查数组中是否至少有一个元素满足某个条件,可以使用 some 方法:

    if (Array.isArray(data[currentKey]) && !data[currentKey].some(item => filterNestedJSON(item, value))) {
      return false;
    }
    
  3. 问:如何提高代码的性能?

    答: 对于大型数据集,可以考虑使用缓存机制来存储中间结果,避免重复计算。

  4. 问:如何处理循环引用?

    答: 你可以在递归函数中添加一个参数来跟踪已经访问过的对象,避免无限递归。

  5. 问:还有其他方法可以实现多键值对过滤吗?

    答: 是的,你也可以使用第三方库,例如 lodash 或 ramda,它们提供了更简洁的 API 来处理 JSON 数据。