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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 17:25:05