如何遍历TypeScript类的属性并获取各属性的预期类型?
问题解答
TypeScript编译后会丢失类型信息,所以直接遍历未实例化的类本身,无法在运行时获取属性的类型标注(比如number、string、Price[]这类信息)。不过可以通过以下几种方案实现你的需求:
1. 装饰器+元数据方案(最可靠)
借助reflect-metadata库,结合装饰器给类属性注入类型元数据,就能在运行时读取属性的具体类型。
实现步骤:
- 安装依赖:
npm install reflect-metadata - 在
tsconfig.json中开启emitDecoratorMetadata和experimentalDecorators选项 - 编写装饰器并给类属性标记类型:
import 'reflect-metadata'; // 定义装饰器,用于记录属性类型 function Type(type: any) { return function(target: any, propertyKey: string) { Reflect.defineMetadata('design:type', type, target, propertyKey); // 数组类型额外标记元素类型 if (type === Array) { Reflect.defineMetadata('design:elementType', arguments[1], target, `${propertyKey}:element`); } }; } export class Price {} export class Product { @Type(Number) id: number; @Type(String) title: string; @Type(Array) @Type(Price) prices: Price[]; constructor() { this.id = null; this.title = null; this.prices = []; } } // 获取属性类型的工具函数 function getPropertyTypes(target: any): Record<string, string> { const propertyNames = Object.getOwnPropertyNames(target.prototype).filter(name => name !== 'constructor'); const types: Record<string, string> = {}; propertyNames.forEach(name => { const type = Reflect.getMetadata('design:type', target.prototype, name); if (type === Array) { const elementType = Reflect.getMetadata('design:elementType', target.prototype, `${name}:element`); types[name] = `${elementType.name}[]`; } else { types[name] = type.name.toLowerCase(); } }); return types; } // 使用示例 const productTypes = getPropertyTypes(Product); console.log(productTypes); // 输出:{ id: 'number', title: 'string', prices: 'Price[]' }
2. 手动维护类型配置(轻量方案)
如果不想引入额外依赖,可以手动为每个类编写类型配置对象,映射时直接读取配置:
export class Price {} export class Product { id: number; title: string; prices: Price[]; constructor() { this.id = null; this.title = null; this.prices = []; } } // 手动定义Product的类型映射配置 const ProductTypeConfig = { id: 'number', title: 'string', prices: { type: 'array', elementModel: Price } }; // 映射工具函数 function mapToModel(apiData: any, config: any, modelClass: any) { const model = new modelClass(); Object.keys(config).forEach(key => { const propConfig = config[key]; if (propConfig.type === 'array' && propConfig.elementModel) { model[key] = apiData[key]?.map(item => mapToModel(item, getModelConfig(propConfig.elementModel), propConfig.elementModel)) || []; } else { model[key] = apiData[key]; } }); return model; } // 根据类获取对应配置的辅助函数 function getModelConfig(modelClass: any) { if (modelClass === Product) return ProductTypeConfig; if (modelClass === Price) return {}; // 可扩展Price的配置 return {}; }
3. 临时实例推断(局限性较大)
可以创建类的临时实例,通过实例属性值推断类型,但这种方式对引用类型的判断有限(比如只能知道是数组,无法直接获取元素的模型类型):
export class Price {} export class Product { id: number; title: string; prices: Price[]; constructor() { this.id = null; this.title = null; this.prices = []; } } function getPropertyTypesFromInstance(modelClass: any): Record<string, string> { const tempInstance = new modelClass(); const types: Record<string, string> = {}; Object.keys(tempInstance).forEach(key => { const value = tempInstance[key]; if (Array.isArray(value)) { types[key] = 'array'; } else { types[key] = typeof value; } }); return types; } // 使用示例 const types = getPropertyTypesFromInstance(Product); console.log(types); // 输出:{ id: 'object', title: 'object', prices: 'array' } // 注意:初始值为null的属性,typeof会返回object,存在局限性
总结:装饰器+元数据的方案能准确获取属性类型(包括子模型数组的类型),是最推荐的方案;手动配置适合小型项目;临时实例方案仅能作为补充,局限性较大。
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

