
本文旨在解决在过滤深度嵌套的对象数组时,如何保留匹配项的父级层级结构的问题。通过将数据结构统一为使用`children`键表示子项,并采用自定义的递归过滤函数,可以有效地实现这一目标,确保仅包含符合条件的子项及其所有祖先节点,同时保持数据结构的完整性。
在处理复杂的、多层级的数据结构(如产品目录、组织架构等)时,我们经常需要根据特定条件对数据进行深度过滤。一个常见的挑战是,当子节点满足过滤条件时,如何确保其所有父节点也被保留在结果中,即使这些父节点本身不直接满足过滤条件。传统的深度过滤库,如deepdash的_.filterDeep,在默认情况下可能会移除不匹配的父节点,导致结果丢失上下文信息。
深度嵌套数据过滤的挑战
考虑一个包含多层产品分类的数组,每个产品都有唯一的code。我们的目标是根据code进行搜索,并返回所有匹配的产品及其完整的祖先路径(从顶层分类到产品本身)。
原始数据结构可能如下所示:
const products_array = [ { name: 'Food to Go', filter: 'food', categories_list: [ // 第一层子项 { name: 'Bepulp Compostable', sub_categories: [ // 第二层子项 { name: 'BOWLS & CONTAINERS', products: [ // 第三层子项 { type: 'RECTANGULAR', products_list: [ // 第四层子项 (实际产品) { color: 'natural', code: 'PAP46120', description: 'Rectangular tray 600ml' }, { color: 'natural', code: 'PUL46130', description: 'Rectangular tray 950ml' }, // ...更多产品 ] }, // ...更多类型 ] }, // ...更多子分类 ], }, // ...更多分类 ], }, // ...更多顶级产品组];
如果使用 deepdash 的 _.filterDeep 并且只在叶子节点上匹配,结果可能会是这样的:
const current_output = [ { categories_list: [ { sub_categories: [ { products: [ { products_list: [ { code: 'PUL46130' }, { code: 'PUL51601' }, // ... ] } ] } ] } ] }];
可以看到,current_output 仅保留了匹配项及其直接父级的空壳,丢失了所有父节点的其他属性(如name, filter, color等),并且层级结构也变得不完整。
期望的输出
我们期望的输出是这样的:如果某个产品(例如 code: ‘PUL46130’)匹配,那么它的所有父级(RECTANGULAR 类型、BOWLS & CONTAINERS 子分类、Bepulp Compostable 分类、Food to Go 顶级组)都应该被完整地保留下来,包括它们的非子项属性。
const expected_output = [ { name: 'Food to Go', filter: 'food', color: '#f9dd0a', categories_list: [ { name: 'Bepulp Compostable', sub_categories: [ { name: 'BOWLS & CONTAINERS', products: [ { type: 'RECTANGULAR', products_list: [ { color: 'natural', code: 'PUL46130', description: 'Rectangular tray 950ml' }, { color: 'clear', code: 'PUL51601', description: 'rPET lid' }, // ...其他匹配产品 ] }, // ...其他匹配类型 ] }, // ...其他匹配子分类 ], }, // ...其他匹配分类 ], }, // ...其他匹配顶级产品组];
解决方案:统一子节点键与递归过滤
为了优雅地实现这种带有层级保留的深度过滤,一个有效的策略是统一数据结构中表示子节点的键名,例如全部使用children。然后,我们可以编写一个自定义的递归过滤函数。
1. 数据结构预处理(如果需要)
如果你的原始数据结构像products_array那样,子节点键名不统一(categories_list, sub_categories, products, products_list),你需要先将其转换为统一使用children键的结构。例如:
const transformed_products = [ { name: 'Food to Go', filter: 'food', color: '#f9dd0a', children: [ // 原来的 categories_list { name: 'Bepulp Compostable', children: [ // 原来的 sub_categories { name: 'BOWLS & CONTAINERS', children: [ // 原来的 products { name: 'RECTANGULAR', // 或者 type 属性 children: [ // 原来的 products_list { name: 'PUL46120', color: 'natural', code: 'PUL46120', description: 'Rectangular tray 600ml' }, { name: 'PUL46130', color: 'natural', code: 'PUL46130', description: 'Rectangular tray 950ml' }, // ... ] }, // ... ] }, // ... ], }, // ... ], }, // ...];
注意:此转换步骤本身不在本教程的过滤函数中实现,但它是使以下递归过滤函数简洁高效的前提。
2. 实现递归过滤函数
核心思路是:对于每个节点,首先检查它是否满足过滤条件。如果满足,则保留该节点。如果它不满足,但它的任何子节点满足过滤条件,那么也保留该节点(因为它是一个祖先),并对其子节点进行递归过滤。
/** * 深度复制对象,避免直接修改原数据 * @param {Object} o - 要复制的对象 * @returns {Object} 复制后的对象 */function copy(o) { return Object.assign({}, o);}/** * 递归过滤深度嵌套的对象数组,并保留匹配项的父级层级 * @param {Array
3. 示例用法
假设我们已经将原始数据转换成了 transformed_products 结构。
// 假设 transformed_products 已经准备好// ... (transformed_products 数据结构如上所示)const searchVal = 'PUL'; // 搜索包含 'PUL' 的产品const filteredResult = filterDeepHierarchy(transformed_products, searchVal);console.log(JSON.stringify(filteredResult, null, 2));
运行上述代码,你将得到一个与期望输出类似的结果,其中所有匹配PUL的产品及其完整的父级层级都被保留。
注意事项与总结
数据结构统一化是关键:此解决方案依赖于一个统一的children键来表示所有子节点。如果你的原始数据结构不满足这一点,你需要先进行数据转换。这通常是一个预处理步骤,可以通过一个单独的递归函数实现。Immutability (不变性):copy 函数的使用确保了原始数据在过滤过程中不会被修改,这在函数式编程和避免副作用方面是一个好的实践。过滤逻辑可扩展性:recursiveFilter 函数中的 isMatch 逻辑可以轻松扩展,以检查更多属性或实现更复杂的匹配规则(例如正则表达式、多条件组合等)。性能考虑:对于非常庞大的深度嵌套数据集,递归操作可能会有性能开销。在极端情况下,可能需要考虑迭代方法或优化递归深度。然而,对于大多数常见场景,这种递归方法是清晰且高效的。空节点处理:在recursiveFilter中,如果一个节点既不直接匹配,也没有匹配的子节点,它将被过滤掉(返回null,然后filter函数会将其移除)。这正是我们期望的行为。
通过采用这种统一数据结构和自定义递归过滤的方法,我们可以有效地解决在深度嵌套对象数组中保留父级层级的问题,从而生成既精确又具有完整上下文的过滤结果。
以上就是使用递归过滤深度嵌套对象数组并保留父级层级的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1529894.html
微信扫一扫
支付宝扫一扫