
本文介绍一种声明式、可递归的白名单字段提取方案,通过定义目标结构模板,自动从深层嵌套对象中精准提取所需字段,支持对象、数组及混合嵌套场景,避免手动逐层解构或硬编码路径。
本文介绍一种声明式、可递归的白名单字段提取方案,通过定义目标结构模板,自动从深层嵌套对象中精准提取所需字段,支持对象、数组及混合嵌套场景,避免手动逐层解构或硬编码路径。
在实际开发中,常需从复杂嵌套数据(如 API 响应)中仅提取特定字段构建轻量 DTO 或视图模型。若采用“全量拷贝 + 删除黑名单”方式,不仅性能冗余,更违背白名单安全原则;而手写多层解构(如 data.plan?.planDetails?.customproductAllocations?.[0]?.allocationDetail?.name)则极易出错、难以维护。
理想方案应具备:✅ 声明式定义(清晰表达“我要什么”)
✅ 递归支持(自动处理任意深度嵌套对象与数组)
✅ 类型友好(结构即契约,便于 TypeScript 推导)
✅ 零依赖(不强制绑定 Lodash 等工具库)
以下是一个生产就绪的 copyRequiredProperties 工具函数,其核心思想是:将目标结构作为“蓝图”,递归比对源对象并提取匹配字段。
function copyRequiredProperties<t extends record any>, R extends Record<string any>>(
obj: T,
requiredKeys: R
): { [K in keyof R]: K extends keyof T ? T[K] : never } {
const result: Record<string any> = {};
for (const [key, spec] of Object.entries(requiredKeys)) {
// 1. 基础字段:spec 为 undefined → 直接拷贝
if (spec === undefined) {
if (obj && key in obj) {
result[key] = obj[key];
}
continue;
}
// 2. 数组字段:spec 是数组 → 逐项递归处理
if (Array.isArray(spec)) {
if (!Array.isArray(obj?.[key as keyof T])) {
continue;
}
result[key] = [];
const itemSpec = spec[0]; // 取首个元素作为数组项的结构模板
for (const item of obj[key as keyof T] as any[]) {
if (itemSpec && typeof itemSpec === 'object') {
result[key].push(copyRequiredProperties(item, itemSpec));
} else {
result[key].push(item); // 原样保留(如 spec: [null] 表示全量保留)
}
}
continue;
}
// 3. 嵌套对象:spec 是对象 → 递归调用
if (typeof spec === 'object' && spec !== null && !Array.isArray(spec)) {
if (obj && typeof obj[key as keyof T] === 'object' && obj[key as keyof T] !== null) {
result[key] = copyRequiredProperties(obj[key as keyof T], spec);
}
continue;
}
}
return result as any;
}</string></string></t>
✅ 使用示例:精准提取你的 data 对象
根据你提供的数据结构,我们定义如下白名单蓝图(只声明需要的字段,忽略所有其他键):
const requiredShape = {
productId: undefined,
time: undefined,
member: {
teamMembers: [
{
roles: undefined,
type: undefined,
name: undefined,
},
],
},
plan: {
id: undefined,
lastModifiedDate: undefined,
planDetails: {
planName: undefined,
status: undefined,
customproductAllocations: [
{
id: undefined,
allocationDetail: {
name: undefined,
dollar: undefined,
allocations: [
{
id: undefined,
name: undefined,
},
],
},
},
],
products: {
inital: undefined,
externalproducts: [
{
id: undefined,
name: undefined,
},
],
productAllocation: {
productAllocation: [
{
id: undefined,
category: undefined,
},
],
},
},
analysis: {
analysisA: {
id: undefined,
key: undefined,
},
analysisB: {
id: undefined,
key: undefined,
},
},
},
},
};
// 一行调用,生成精简结果
const extracted = copyRequiredProperties(data, requiredShape);
⚠️ 注意事项与最佳实践
- 空值安全:函数内部已做 obj?.[key] 检查,源对象缺失字段时自动跳过,不会抛错;
- 数组一致性:spec 中数组必须包含一个模板对象(如 [ { id: undefined } ]),用于描述每一项结构;若只需原样保留整个数组,可用 [null](此时 itemSpec 为 null,走 else 分支);
- 类型推导:配合 TypeScript,requiredShape 的类型可被严格推导,IDE 能提供完整补全与错误提示;
- 性能考量:时间复杂度为 O(N),其中 N 是 requiredShape 中定义的字段总数,远优于遍历整个原始对象;
- 扩展性:如需支持函数转换(如日期格式化)、默认值填充,可在 spec 中加入元信息(如 { $transform: Date.parse }),稍作增强即可。
此方案将“提取逻辑”从代码转移到声明式配置,大幅提升可读性、可测试性与可维护性——当你下次面对新接口时,只需更新 requiredShape,无需修改提取逻辑本身。










