0

0

使用递归过滤深度嵌套对象数组并保留父级层级

霞舞

霞舞

发布时间:2025-10-16 11:18:01

|

1008人浏览过

|

来源于php中文网

原创

使用递归过滤深度嵌套对象数组并保留父级层级

本文旨在解决在过滤深度嵌套的对象数组时,如何保留匹配项的父级层级结构的问题。通过将数据结构统一为使用`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。然后,我们可以编写一个自定义的递归过滤函数。

Batch GPT
Batch GPT

使用AI批量处理数据、自动执行任务

下载

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} data - 统一使用 'children' 键的深度嵌套数据数组
 * @param {string} searchVal - 搜索值 (不区分大小写)
 * @returns {Array} 过滤后的数据数组,保留了匹配项的父级层级
 */
function filterDeepHierarchy(data, searchVal) {
    // 将搜索值转换为小写,方便不区分大小写匹配
    const lowerSearchVal = searchVal.toLowerCase();

    // 递归过滤函数
    function recursiveFilter(node) {
        // 检查当前节点是否直接匹配
        // 这里可以扩展为检查更多属性,例如 name, description, code 等
        const isMatch = (node.name && node.name.toLowerCase().includes(lowerSearchVal)) ||
                        (node.description && node.description.toLowerCase().includes(lowerSearchVal)) ||
                        (node.code && node.code.toLowerCase().includes(lowerSearchVal));

        // 如果当前节点有子节点
        if (node.children && node.children.length > 0) {
            // 递归过滤子节点。
            // 关键在于:先对子节点进行深拷贝并过滤,然后将过滤后的结果重新赋值给当前节点的 children 属性。
            // 如果过滤后的 children 数组长度大于0,说明有子节点匹配,则当前父节点也应该被保留。
            const filteredChildren = node.children.map(copy).filter(recursiveFilter);
            if (filteredChildren.length > 0) {
                // 如果有匹配的子节点,则保留当前节点,并更新其 children
                const newNode = copy(node); // 复制当前节点以保持不变性
                newNode.children = filteredChildren;
                return newNode;
            }
        }

        // 如果当前节点直接匹配,或者其子节点中有匹配项(已在上面处理),则保留
        // 否则,不保留
        return isMatch ? copy(node) : null; // 如果是叶子节点且匹配,也复制并返回
    }

    // 对顶层数组进行过滤
    return data.map(copy).filter(recursiveFilter);
}

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的产品及其完整的父级层级都被保留。

注意事项与总结

  1. 数据结构统一化是关键:此解决方案依赖于一个统一的children键来表示所有子节点。如果你的原始数据结构不满足这一点,你需要先进行数据转换。这通常是一个预处理步骤,可以通过一个单独的递归函数实现。
  2. Immutability (不变性):copy 函数的使用确保了原始数据在过滤过程中不会被修改,这在函数式编程和避免副作用方面是一个好的实践。
  3. 过滤逻辑可扩展性:recursiveFilter 函数中的 isMatch 逻辑可以轻松扩展,以检查更多属性或实现更复杂的匹配规则(例如正则表达式、多条件组合等)。
  4. 性能考虑:对于非常庞大的深度嵌套数据集,递归操作可能会有性能开销。在极端情况下,可能需要考虑迭代方法或优化递归深度。然而,对于大多数常见场景,这种递归方法是清晰且高效的。
  5. 空节点处理:在recursiveFilter中,如果一个节点既不直接匹配,也没有匹配的子节点,它将被过滤掉(返回null,然后filter函数会将其移除)。这正是我们期望的行为。

通过采用这种统一数据结构和自定义递归过滤的方法,我们可以有效地解决在深度嵌套对象数组中保留父级层级的问题,从而生成既精确又具有完整上下文的过滤结果。

相关专题

更多
js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

508

2023.06.20

正则表达式不包含
正则表达式不包含

正则表达式,又称规则表达式,,是一种文本模式,包括普通字符和特殊字符,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式的文本。php中文网给大家带来了有关正则表达式的相关教程以及文章,希望对大家能有所帮助。

247

2023.07.05

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

724

2023.07.05

java正则表达式匹配字符串
java正则表达式匹配字符串

在Java中,我们可以使用正则表达式来匹配字符串。本专题为大家带来java正则表达式匹配字符串的相关内容,帮助大家解决问题。

209

2023.08.11

正则表达式空格
正则表达式空格

正则表达式空格可以用“s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。本专题为大家提供正则表达式相关的文章、下载、课程内容,供大家免费下载体验。

343

2023.08.31

Python爬虫获取数据的方法
Python爬虫获取数据的方法

Python爬虫可以通过请求库发送HTTP请求、解析库解析HTML、正则表达式提取数据,或使用数据抓取框架来获取数据。更多关于Python爬虫相关知识。详情阅读本专题下面的文章。php中文网欢迎大家前来学习。

293

2023.11.13

正则表达式空格如何表示
正则表达式空格如何表示

正则表达式空格可以用“s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。想了解更多正则表达式空格怎么表示的内容,可以访问下面的文章。

229

2023.11.17

正则表达式中如何匹配数字
正则表达式中如何匹配数字

正则表达式中可以通过匹配单个数字、匹配多个数字、匹配固定长度的数字、匹配整数和小数、匹配负数和匹配科学计数法表示的数字的方法匹配数字。更多关于正则表达式的相关知识详情请看本专题下面的文章。php中文网欢迎大家前来学习。

526

2023.12.06

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.1万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号