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

如何从TypeScript动态类型模型推导关联接口?

解决方案

可以通过扩展TypeScript工具类型和模型生成逻辑,实现无需重复定义接口的动态类型推导,同时支持命名风格转换与类型安全的导出方法。以下是具体实现步骤:

1. 定义命名风格转换工具类型

先实现蛇形与小驼峰命名的双向类型映射:

// 蛇形转小驼峰
type SnakeToCamel<S extends string> = S extends `${infer P}_${infer R}`
  ? `${Lowercase<P>}${Capitalize<SnakeToCamel<R>>}`
  : Lowercase<S>;

// 小驼峰转蛇形
type CamelToSnake<S extends string> = S extends `${infer P}${infer R}`
  ? `${P extends Capitalize<P> ? "_" : ""}${Lowercase<P>}${CamelToSnake<R>}`
  : S;

2. 实现扁平对象的动态类型推导

基于模型属性配置,自动提取原始值类型并支持命名风格转换:

// 提取属性对应的原始值类型(处理Date、嵌套模型、数组等场景)
type ExtractPrimitiveType<T> = 
  T extends typeof String ? string :
  T extends typeof Number ? number :
  T extends typeof Boolean ? boolean :
  T extends typeof Date ? string : // 导出时Date转为ISO字符串
  T extends typeof Array<infer U> ? ExtractPrimitiveType<U>[] :
  T extends (...args: any[]) => infer R ? ExtractPrimitiveType<R> :
  T extends { new(): infer Instance } ? FlattenedObject<Instance> :
  never;

// 基础扁平对象类型(保持原命名风格)
type FlattenedObject<T> = T extends { __attributes: infer Attrs }
  ? { [K in keyof Attrs]: ExtractPrimitiveType<Attrs[K]> }
  : never;

// 支持自定义命名风格的扁平对象类型
type CasedFlattenedObject<T, CaseTransform extends (key: string) => string> = 
  T extends { __attributes: infer Attrs }
  ? { 
      [K in keyof Attrs as CaseTransform extends ((k: string) => infer R) ? R : never]: 
      ExtractPrimitiveType<Attrs[K]> 
    }
  : never;

// 预定义蛇形命名的扁平对象类型
type SnakeCasedFlattenedObject<T> = CasedFlattenedObject<T, CamelToSnake>;

// 预定义小驼峰命名的扁平对象类型
type CamelCasedFlattenedObject<T> = CasedFlattenedObject<T, SnakeToCamel>;

3. 扩展模型生成函数,添加类型安全的转换方法

修改ModelFromAttributes,给模型类注入导出方法,确保返回值类型自动推导:

function ModelFromAttributes<Attrs extends Record<string, any>>(attrs: Attrs) {
  return class BaseModel {
    // 保存属性配置,用于类型推导
    static __attributes = attrs;
    __attributes = attrs;

    constructor(data: Partial<{ [K in keyof Attrs]: any }>) {
      // 原有的实例化逻辑:将输入数据转换为对应类型
      Object.keys(attrs).forEach(key => {
        const type = attrs[key];
        this[key as keyof Attrs] = typeof type === 'function' 
          ? type(data[key]) 
          : Array.isArray(type) 
            ? data[key]?.map(item => type[0](item)) 
            : new type(data[key]);
      });
    }

    // 导出为原始扁平对象(保持原命名风格)
    toFlattenedObject(): FlattenedObject<this> {
      const result: Record<string, any> = {};
      Object.keys(this.__attributes).forEach(key => {
        const value = this[key as keyof Attrs];
        if (value instanceof Date) {
          result[key] = value.toISOString();
        } else if (Array.isArray(value)) {
          result[key] = value.map(item => item.toFlattenedObject?.() ?? item);
        } else if (value instanceof BaseModel) {
          result[key] = value.toFlattenedObject();
        } else {
          result[key] = value;
        }
      });
      return result as FlattenedObject<this>;
    }

    // 导出为指定命名风格的扁平对象
    toCasedFlattenedObject<CaseFn extends (key: string) => string>(
      caseTransform: CaseFn
    ): CasedFlattenedObject<this, CaseFn> {
      const result: Record<string, any> = {};
      Object.keys(this.__attributes).forEach(key => {
        const casedKey = caseTransform(key);
        const value = this[key as keyof Attrs];
        if (value instanceof Date) {
          result[casedKey] = value.toISOString();
        } else if (Array.isArray(value)) {
          result[casedKey] = value.map(item => item.toFlattenedObject?.() ?? item);
        } else if (value instanceof BaseModel) {
          result[casedKey] = value.toFlattenedObject();
        } else {
          result[casedKey] = value;
        }
      });
      return result as CasedFlattenedObject<this, CaseFn>;
    }
  };
}

4. 使用示例

// 定义嵌套模型
class Pet extends ModelFromAttributes({
  name: String,
  age: Number
}) {}

// 定义主模型
class Person extends ModelFromAttributes({
  joined: Date,
  name: String,
  first_name: String,
  last_name: String,
  age: Number,
  skills: [String],
  locale: (v: any) => (v === 'vacation' ? null : String),
  pets: [Pet],
}) {}

// 实例化模型
const person = new Person({
  joined: new Date(),
  name: "John",
  first_name: "John",
  last_name: "Doe",
  age: 30,
  skills: ["TypeScript", "JavaScript"],
  locale: "work",
  pets: [{ name: "Buddy", age: 5 }],
});

// 导出原风格扁平对象(类型自动推导为FlattenedObject<Person>)
const flatObj = person.toFlattenedObject();
// 导出小驼峰风格扁平对象(类型自动推导为CamelCasedFlattenedObject<Person>)
const camelObj = person.toCasedFlattenedObject((key) => 
  key.replace(/_([a-z])/g, (_, c) => c.toUpperCase())
);
// 导出蛇形风格扁平对象(类型自动推导为SnakeCasedFlattenedObject<Person>)
const snakeObj = person.toCasedFlattenedObject((key) => 
  key.replace(/([A-Z])/g, "_$1").toLowerCase()
);

该方案实现了:

  • 无需手动编写FlattenedObject等接口,完全通过类型推导生成
  • 转换方法的返回值具备完整的智能提示与类型检查
  • 支持自定义命名风格转换,同时兼容嵌套模型与复杂属性类型

内容的提问来源于stack exchange,提问作者c.m

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 02:45:18