如何在TypeScript中动态清理对象,仅保留目标接口字段?
问题:动态清理TypeScript扩展类型的多余字段
场景描述
我定义了如下TypeScript接口:
export interface PickingOrderItem { itemId: string; productId: string; // 其他字段 }
在Angular环境中,我扩展出包含额外数据的类型:
export interface PickingItemWithArticle extends PickingOrderItem{ article: Article; }
现在需要在发送请求到后端前清理这些运行时数据——额外字段不在API定义里。我想要一个动态通用的清理方案,就算后续PickingItemWithArticle再扩展新的额外字段,也能自动清理掉所有不属于目标类型的字段。
我尝试写了泛型函数,但不知道怎么自动获取目标类型的键:
removeDataOfType<TARGET, INPUT extends TARGET>(pickingOrderItemToSave: INPUT): TARGET { const typesToRemove = ['article'];// 待优化:自动获取TARGET与INPUT的差异字段 const allKeys = Object.keys(pickingOrderItemToSave); const deltaKeys = ??// 如何获取TARGET的键? const cleanedPickingOrderItemToSave: TARGET = {...pickingOrderItemToSave} as TARGET; typesToRemove.forEach(key => delete pickingOrderItemToSave[key]); return cleanedPickingOrderItemToSave as TARGET; }
期望调用方式:
const itemWithArticle: PickingItemWithArticle = {} as PickingItemWithArticle; const cleanedArticle:PickingOrderItem = removeDataOfType<PickingOrderItem>(itemWithArticle);
我知道TypeScript类型编译后会被移除,所以想找无需硬编码删除字段的解决方案。试过很多类型辅助工具,但都只在类型层面生效,我需要在不实例化目标对象的前提下拿到它的键。
可行解决方案
方法1:复用目标字段常量(最直接)
因为TypeScript类型在运行时不存在,必须给函数提供运行时的目标字段列表。可以把目标类型的键做成常量,同时用它推导类型,做到修改一处同步所有:
// 定义PickingOrderItem的键常量,同时推导类型 const PICKING_ORDER_ITEM_KEYS = ['itemId', 'productId'] as const; export type PickingOrderItem = { [K in typeof PICKING_ORDER_ITEM_KEYS[number]]: string; }; // 通用清理函数 removeExtraFields<TARGET>(input: unknown, allowedKeys: readonly string[]): TARGET { const cleaned = {} as TARGET; allowedKeys.forEach(key => { if (input && typeof input === 'object' && key in input) { cleaned[key as keyof TARGET] = input[key as keyof typeof input]; } }); return cleaned; } // 调用示例 const cleanedItem = removeExtraFields<PickingOrderItem>(itemWithArticle, PICKING_ORDER_ITEM_KEYS);
方法2:利用Angular类的元数据
如果你的目标类型是Angular实体类,可以用reflect-metadata通过装饰器标记允许的字段,从类的元数据中获取键:
import 'reflect-metadata'; // 自定义装饰器:标记允许保留的字段 function AllowField(target: any, propertyKey: string) { const allowedFields = Reflect.getMetadata('allowedFields', target.constructor) || []; allowedFields.push(propertyKey); Reflect.defineMetadata('allowedFields', allowedFields, target.constructor); } // 把接口改成类(或者让类实现接口) class PickingOrderItem { @AllowField itemId: string; @AllowField productId: string; } // 清理函数 function cleanToClass<T>(input: any, targetClass: new () => T): T { const allowedKeys = Reflect.getMetadata('allowedFields', targetClass) || []; const instance = new targetClass(); allowedKeys.forEach(key => { if (input[key] !== undefined) { instance[key as keyof T] = input[key]; } }); return instance; } // 调用示例 const cleanedItem = cleanToClass(itemWithArticle, PickingOrderItem);
方法3:JSON Schema校验+清理
如果需要同时做数据校验,可以为目标类型定义JSON Schema,用ajv库自动移除额外字段:
import Ajv from 'ajv'; const ajv = new Ajv({ removeAdditional: true }); // 定义PickingOrderItem的JSON Schema const pickingOrderItemSchema = { type: 'object', properties: { itemId: { type: 'string' }, productId: { type: 'string' } }, required: ['itemId', 'productId'], additionalProperties: false }; const validate = ajv.compile(pickingOrderItemSchema); function cleanWithSchema<T>(input: any, schema: object): T { validate(input); // 校验通过后,input已自动移除额外字段 return input as T; } // 调用示例 const cleanedItem = cleanWithSchema<PickingOrderItem>(itemWithArticle, pickingOrderItemSchema);
关键说明
TypeScript的类型系统是纯编译时的,运行时完全不存在,所以不可能直接从类型获取键。所有通用方案的核心都是把编译时的类型信息,通过常量、元数据、Schema等方式同步到运行时,让清理逻辑能拿到要保留的字段列表。
内容的提问来源于stack exchange,提问作者marko
相关产品推荐
相关产品推荐

