
本文介绍一种高效、简洁且可扩展的方式,利用 JSON 序列化/反序列化的 reviver 参数,递归过滤掉所有“末级仅含空 parent、无实际业务数据(如 child.id 或 child.name 等关键字段)”的对象节点,精准保留具有真实终端内容(如 loan 类型叶子节点)的完整子树。
本文介绍一种高效、简洁且可扩展的方式,利用 json 序列化/反序列化的 `reviver` 参数,递归过滤掉所有“末级仅含空 parent、无实际业务数据(如 `child.id` 或 `child.name` 等关键字段)”的对象节点,精准保留具有真实终端内容(如 `loan` 类型叶子节点)的完整子树。
在处理深度嵌套的树形结构数据(例如从 API 获取的超 5000 行组织架构或产品分类树)时,常需剔除那些“形同虚设”的中间节点:它们拥有 parent 字段,但其 child 属性要么为 undefined、null,要么为空数组,甚至仅包含同样无效的子节点——最终整条路径未承载任何终端业务数据(如 loan 实体中的 name、id 字段)。传统递归 filter 易陷入状态丢失、副作用难控、浅层过滤不彻底等问题。
核心思路:借助 JSON.parse 的 reviver 函数,在反序列化每一层键值对时动态判断并剪枝。
reviver 会在每个属性被解析后立即执行,接收 (key, value) 参数,返回值将替代原值参与后续解析。我们利用这一机制,在遇到 child 数组时,对其执行语义化过滤:仅保留那些 自身具备有效终端数据,或 其子树中存在有效终端数据 的元素。
✅ 关键判定逻辑:一个节点是否“值得保留”?
→ 若 node.child 是数组,则检查其中是否存在至少一个子项满足:
sub.child?.id !== undefined 或 sub.child?.name !== undefined 或 sub.child?.length > 0(若 child 是非空数组);
→ 若 node.child 是普通对象(如 {"name": "loan1", "id": "1000613"}),则直接视为有效终端节点;
→ 否则(如 child: undefined 或 child: {} 或 child: []),该节点应被剔除。
以下为生产就绪的实现方案:
function pruneEmptyBranches(jsonString, terminalKeys = ['id', 'name', 'treeDepth']) {
return JSON.parse(jsonString, (key, value) => {
// 只对 'child' 字段进行特殊处理
if (key === 'child') {
if (Array.isArray(value)) {
// 过滤数组:保留至少有一个子节点具备任意 terminalKey 的项
return value.filter(item => {
const child = item.child;
if (child === null || child === undefined) return false;
// 情况1:child 是终端对象(含 id/name 等)
if (typeof child === 'object' && child !== null && !Array.isArray(child)) {
return terminalKeys.some(k => child[k] !== undefined && child[k] !== null);
}
// 情况2:child 是非空数组(可能还有下级)
if (Array.isArray(child) && child.length > 0) {
return true; // 交给下一层 reviver 继续判断
}
return false;
});
}
// 情况3:child 是终端对象(非数组),直接校验字段
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
return terminalKeys.some(k => value[k] !== undefined && value[k] !== null)
? value
: undefined; // 移除无效 child
}
}
return value;
});
}
// 使用示例(基于问题中提供的 JSON 字符串)
const cleanedData = pruneEmptyBranches(jsonString);
console.log(cleanedData.root); // 仅保留含 "loan" 终端节点的完整子树注意事项与最佳实践:
立即学习“Java免费学习笔记(深入)”;
- ? 不可用于已解析对象:reviver 仅在 JSON.parse 时生效。若数据已是 JS 对象,请先 JSON.stringify(obj) 再 parse,或改用纯递归函数(但性能与简洁性略逊);
- ⚙️ 灵活配置终端标识:通过 terminalKeys 参数可适配不同业务场景(如金融系统用 loanId、电商用 sku);
- ? 深度优先保障完整性:reviver 自底向上执行,确保子节点先被清洗,父节点再依据清洗后的子数组做决策,天然避免“残留空壳”;
- ? 避免修改原数据:该方法始终返回新对象,符合函数式编程原则,无副作用;
- ? 调试建议:在 reviver 中添加 console.log(key, typeof value, Array.isArray(value), value) 快速定位清洗逻辑触发点。
总结而言,此方案以极简代码达成高鲁棒性树形过滤:它不依赖固定层级假设,不侵入原始数据结构,且能精准锚定业务定义的“有效终点”,是处理大规模嵌套 JSON 清洗任务的推荐范式。










