You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 01:01:15