TypeScript装饰器:如何获取类属性类型以处理Strapi数据转换
基于装饰器的Strapi数据转换方案:解决复杂类型映射问题
完全可以继续用装饰器方案实现你的需求,核心问题是如何捕获属性的复杂类型(自定义模型类),下面给出具体的解决思路和代码实现:
核心思路
利用TypeScript的reflect-metadata库获取属性的构造函数类型,结合装饰器存储的Strapi字段映射关系,实现自动的嵌套模型实例化。
步骤1:配置TypeScript与依赖
首先确保tsconfig.json启用装饰器相关配置:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
安装依赖:
npm install reflect-metadata
步骤2:改进装饰器实现
修改装饰器,结合反射元数据存储字段映射和类型信息:
import 'reflect-metadata'; // 定义元数据结构 interface FieldMeta { strapiKey: string; // 存储属性的构造函数,用于判断是否为自定义模型 typeCtor?: new (...args: any[]) => any; // 处理数组类型时,存储元素的构造函数 elementCtor?: new (...args: any[]) => any; } export function field(strapiKey: string, elementCtor?: new (...args: any[]) => any) { return (_target: any, context: ClassFieldDecoratorContext) => { // 初始化模型的全局元数据容器 if (!Reflect.hasMetadata('strapiModelFields', _target.constructor)) { Reflect.defineMetadata('strapiModelFields', {}, _target.constructor); } const fieldsMeta = Reflect.getMetadata('strapiModelFields', _target.constructor) as Record<string, FieldMeta>; // 通过反射获取属性的构造函数(基础类型/Array) const typeCtor = Reflect.getMetadata('design:type', _target, context.name); fieldsMeta[context.name] = { strapiKey, typeCtor, elementCtor }; }; }
步骤3:实现通用转换函数
编写从Strapi数据到模型实例的转换函数,自动处理嵌套模型和数组:
export function fromStrapi<T>(modelClass: new () => T, strapiData: any): T { const instance = new modelClass(); const fieldsMeta = Reflect.getMetadata('strapiModelFields', modelClass) as Record<string, FieldMeta>; for (const [propName, meta] of Object.entries(fieldsMeta)) { const strapiValue = strapiData[meta.strapiKey]; if (strapiValue == null) continue; // 判断是否为自定义模型类(通过检查是否存在模型元数据) const isCustomModel = meta.typeCtor && Reflect.hasMetadata('strapiModelFields', meta.typeCtor); // 判断是否为模型数组 const isModelArray = meta.typeCtor === Array && meta.elementCtor && Reflect.hasMetadata('strapiModelFields', meta.elementCtor); if (isCustomModel) { instance[propName] = fromStrapi(meta.typeCtor, strapiValue); } else if (isModelArray) { instance[propName] = strapiValue.map((item: any) => fromStrapi(meta.elementCtor!, item)); } else { // 基础类型直接赋值 instance[propName] = strapiValue; } } return instance; } // 反向转换(模型到Strapi格式)同理可实现 export function toStrapi<T>(modelInstance: T): any { const modelClass = modelInstance.constructor; const fieldsMeta = Reflect.getMetadata('strapiModelFields', modelClass) as Record<string, FieldMeta>; const strapiData: any = {}; for (const [propName, meta] of Object.entries(fieldsMeta)) { const modelValue = modelInstance[propName as keyof T]; if (modelValue == null) continue; const isCustomModel = meta.typeCtor && Reflect.hasMetadata('strapiModelFields', meta.typeCtor); const isModelArray = meta.typeCtor === Array && meta.elementCtor && Reflect.hasMetadata('strapiModelFields', meta.elementCtor); if (isCustomModel) { strapiData[meta.strapiKey] = toStrapi(modelValue); } else if (isModelArray) { strapiData[meta.strapiKey] = modelValue.map((item: any) => toStrapi(item)); } else { strapiData[meta.strapiKey] = modelValue; } } return strapiData; }
步骤4:使用示例
// 定义嵌套模型 class User { @field("user_name") name: string; @field("user_age") age: number; } // 主模型 class Post { @field("post_title") title: string; @field("post_content") content: string; // 嵌套模型 @field("author") author: User; // 模型数组,需传入元素构造函数 @field("related_posts", Post) relatedPosts: Post[]; } // 模拟Strapi返回数据 const strapiRawData = { post_title: "Strapi装饰器方案实践", post_content: "解决复杂类型映射问题", author: { user_name: "测试用户", user_age: 28 }, related_posts: [ { post_title: "第一篇文章", author: { user_name: "作者A", user_age: 30 } } ] }; // 转换为模型实例 const postModel = fromStrapi(Post, strapiRawData); console.log(postModel.author.name); // 输出:测试用户 console.log(postModel.relatedPosts[0].title); // 输出:第一篇文章 // 反向转换回Strapi格式 const strapiData = toStrapi(postModel);
为什么不放弃装饰器方案?
- 映射关系与模型类紧耦合,代码更整洁,新增字段/模型时无需修改转换函数
- 扩展性强,后续可在装饰器中添加更多配置(比如字段格式化、默认值等)
- 类型安全,TypeScript会自动校验模型属性的类型
内容的提问来源于stack exchange,提问作者TheJof
相关产品推荐
相关产品推荐

