Skip to content
概述
deepFilter 对 JSON 对象或数组执行深度递归过滤。它接收一个谓词函数,作用于所有非容器值(基本类型和 null),并根据谓词结果决定保留还是删除。容器(对象、数组)不会直接传给谓词,而是由子元素是否被清空来间接决定自身是否保留。整个过程原地修改传入的数据,不产生副本。
类型定义
ts
type JSONValue =
| null
| boolean
| number
| string
| JSONValue[]
| { [key: string]: JSONValue };
type Obj = Record<string, JSONValue> | JSONValue[];
function deepFilter(
obj: Obj,
fn: (value: JSONValue) => boolean
): Obj | undefined;JSONValue 是递归定义的合法 JSON 值。Obj 限定了顶层参数只接受 JSON 对象或 JSON 数组。fn 只接收非容器值,返回 true 保留,false 删除。
工作原理
处理过程分两层判断。
- 基本值判断 — 到达
null、boolean、number、string时调用fn。若fn返回false,当前函数返回undefined,向上层表示“该值已删除”。 - 容器判断 — 递归完对象所有属性或数组所有元素后,检查自身是否还有剩余键或元素。若容器变空,返回
undefined,让上层将其删除。这种向上传播行为保证不会留下空对象或空数组。
举个传播的例子:对象 { a: { b: -1, c: -2 } } 用 v => (v as number) > 0 过滤。内层对象的 b、c 均被滤除,内层对象返回 undefined,导致顶层对象也变空,最终整个调用返回 undefined。
基本用法
ts
import { deepFilter } from './deepFilter';
const data = {
a: 1,
b: { c: 2, d: -1 },
e: [3, -2, 4],
f: null,
};
const filtered = deepFilter(data, (v) => v !== null && Number(v) > 0);
// filtered => { a: 1, b: { c: 2 }, e: [3, 4] }a满足谓词保留。b中d被移除,c保留,b自身保留。e中负数被原地删除,保留正数。f为null,谓词返回false,属性被删除。
示例
基本筛除
ts
const obj = { x: 5, y: 0 };
deepFilter(obj, (v) => (v as number) > 0);
// => { x: 5 }嵌套清空
ts
const obj = { a: { b: -1, c: -2 } };
deepFilter(obj, (v) => (v as number) > 0);
// => undefined内层对象变空,外层随之变空。
数组清空
ts
const arr = [0, -1];
deepFilter(arr, (v) => (v as number) > 0);
// => undefined混合结构按类型过滤
ts
const data = {
name: 'Alice',
details: { age: 30, city: '' },
orders: [{ id: 1, active: true }, { id: 2, active: false }],
};
const result = deepFilter(data, (v) => {
if (typeof v === 'string') return v.length > 0;
if (typeof v === 'boolean') return v === true;
return true;
});
// result =>
// {
// name: 'Alice',
// details: { age: 30 },
// orders: [{ id: 1, active: true }],
// }谓词对 city 空字符串返回 false,该属性被删除,details 只剩下 age。数组中 active: false 被滤除,保留 active: true 的对象。
参考实现
ts
type JSONValue =
| null
| boolean
| number
| string
| JSONValue[]
| { [key: string]: JSONValue };
type Obj = Record<string, JSONValue> | JSONValue[];
const isObject = (v: unknown): v is Obj =>
typeof v === 'object' && v !== null;
function deepFilter(
obj: Obj,
fn: (value: JSONValue) => boolean
): Obj | undefined {
if (!isObject(obj)) {
return fn(obj) ? obj : undefined;
}
if (Array.isArray(obj)) {
let i = 0;
while (i < obj.length) {
const val = deepFilter(obj[i] as Obj, fn);
if (val === undefined) {
obj.splice(i, 1);
// 索引不动,下一元素自动前移
} else {
obj[i] = val;
i++;
}
}
return obj.length > 0 ? obj : undefined;
}
for (const key in obj) {
const val = deepFilter(obj[key] as Obj, fn);
if (val === undefined) {
delete obj[key];
} else {
obj[key] = val;
}
}
return Object.keys(obj).length > 0 ? obj : undefined;
}数组的原地删除使用 while + splice,而不是 filter,以保证不额外分配新数组。当删除元素时索引不递增,保持指向正确位置。
注意点
- 函数直接修改传入的对象。如果调用方需要保留原始数据,应在调用前自行深克隆。
fn只接收非容器值。如果需要依据容器整体特征(例如“键名以_开头就删除整个对象”)来决定去留,只能在deepFilter外部预先处理或设计不同的 API。- 空对象和空数组在递归结束时向上返回
undefined,导致从父结构中删除。如果希望保留空容器,需要调整返回逻辑,或者在结果上做二次处理。 - 谓词对
null的处理需显式定义,因为typeof null === 'object',如果代码中依赖typeof判断类型,null可能被误归类。
