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

如何获取TypeScript中未初始化的类属性?

问题描述

我想创建一个能接收任意类、接口或对象类型的通用映射器,但传入的多数类都没有初始化属性,示例如下:

export class LoginDTO {
  @IsStringAndNotEmpty()
  email: string;

  @IsStringAndNotEmpty()
  password: string;
}

当我执行下面的genericMapper函数时:

/**
 * 通用映射器,可映射到任意对象、类型、接口或类
 * @param source 要映射属性的源对象
 * @param target 提供要映射的属性键的目标对象/类
 * @returns 目标类型的部分实例
 */
export function genericMapper<T>(source: any, target): T {
  const result = {} as T;

  try {
    const map = isClass(target) ? new target() : target;

    for (const key in map) {
      result[key] = source[key];
    }
  } catch { }

  return result;
}

这个函数无法正常工作,因为TypeScript中未初始化的属性默认不可枚举,但我没办法初始化项目中所有DTO的属性。我试过用Reflect.ownKeys(target.prototype)但没成功,请问怎么获取类的未初始化属性?


解决方案

1. 利用TypeScript元数据(结合装饰器)

你的DTO已经使用了装饰器,只要在tsconfig.json中开启emitDecoratorMetadata和experimentalDecorators编译选项,TypeScript会自动为类属性添加元数据,就能通过Reflect API获取未初始化的属性:

首先配置tsconfig.json:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  }
}

修改映射器函数:

import 'reflect-metadata';

// 判断是否为类的辅助函数
function isClass(target: any): boolean {
  return typeof target === 'function' && target.prototype !== undefined;
}

export function genericMapper<T>(source: any, target: new () => T): Partial<T> {
  const result = {} as Partial<T>;

  try {
    if (isClass(target)) {
      // 获取类原型上的所有自有属性,排除构造函数
      const propertyKeys = Object.getOwnPropertyNames(target.prototype)
        .filter(key => key !== 'constructor');

      // 遍历属性键,从源对象取值
      for (const key of propertyKeys) {
        if (source.hasOwnProperty(key)) {
          result[key as keyof T] = source[key];
        }
      }
    } else {
      // 处理普通对象的情况
      for (const key in target) {
        if (source.hasOwnProperty(key)) {
          result[key as keyof T] = source[key];
        }
      }
    }
  } catch (err) {
    console.error('映射失败:', err);
  }

  return result;
}

2. 手动维护静态属性列表(备选方案)

如果不想依赖元数据编译选项,可以给每个DTO添加静态属性声明所有字段:

export class LoginDTO {
  @IsStringAndNotEmpty()
  email: string;

  @IsStringAndNotEmpty()
  password: string;

  static readonly fields = ['email', 'password'] as const;
}

然后在映射器中读取这个静态属性:

function isClass(target: any): boolean {
  return typeof target === 'function' && target.prototype !== undefined;
}

export function genericMapper<T>(source: any, target: new () => T & { fields?: readonly (keyof T)[] }): Partial<T> {
  const result = {} as Partial<T>;

  try {
    if (isClass(target)) {
      // 优先使用静态字段列表, fallback到原型属性
      const propertyKeys = target.fields || Object.getOwnPropertyNames(target.prototype).filter(key => key !== 'constructor');
      
      for (const key of propertyKeys) {
        if (source.hasOwnProperty(key)) {
          result[key] = source[key];
        }
      }
    } else {
      for (const key in target) {
        if (source.hasOwnProperty(key)) {
          result[key as keyof T] = source[key];
        }
      }
    }
  } catch (err) {
    console.error('映射失败:', err);
  }

  return result;
}

3. 使用第三方工具简化(可选)

如果项目允许引入第三方库,class-transformer专门处理TypeScript类与对象的转换,完美适配带装饰器的DTO:

import { plainToInstance } from 'class-transformer';

// 直接调用即可自动映射所有DTO属性
const loginDto = plainToInstance(LoginDTO, source);

内容的提问来源于stack exchange,提问作者Miguel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 05:05:18