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

求助:为类Zod的TypeScript库实现自定义类型推断

实现类Zod库的TypeScript类型推断

核心思路是通过泛型绑定运行时Schema实例与编译时类型,配合TypeScript的条件类型、索引类型实现自动类型推断。以下是分步实现方案:

1. 定义基础Schema基类

先创建一个带泛型的基类,泛型T表示当前Schema对应的编译时类型,所有具体类型的Schema都继承这个基类:

class Schema<T> {
  // 你的运行时校验逻辑(已实现部分)
  parse(input: unknown): T {
    // 这里替换为你已有的校验逻辑,返回值会自动带上T类型
    return input as T;
  }
}

2. 实现基础类型Schema

为string、number等基础类型创建子类,绑定对应的泛型类型:

class StringSchema extends Schema<string> {
  parse(input: unknown): string {
    if (typeof input !== 'string') throw new Error('Invalid string');
    return input;
  }
}

class NumberSchema extends Schema<number> {
  parse(input: unknown): number {
    if (typeof input !== 'number') throw new Error('Invalid number');
    return input;
  }
}

// 提供Zod风格的便捷创建函数
export const string = () => new StringSchema();
export const number = () => new NumberSchema();

此时使用const strSchema = string();,TypeScript会自动推断strSchema的类型为Schema<string>,调用strSchema.parse("test")返回值类型为string。

3. 实现Object类型的自动类型推断(核心)

这是你困惑的关键部分,需要通过工具类型从运行时的对象Schema结构中提取编译时类型:

第一步:定义类型推断工具

用索引类型和条件类型的infer关键字,将Schema组成的对象转换为对应的TS对象类型:

// 工具类型:从Schema对象中提取编译时类型
type InferSchema<T> = {
  [K in keyof T]: T[K] extends Schema<infer U> ? U : never;
};

这个类型会遍历输入对象的每个属性,取出每个Schema实例对应的泛型类型U,最终组合成完整的对象类型。

第二步:实现ObjectSchema类与创建函数

class ObjectSchema<T> extends Schema<T> {
  private readonly schema: Record<string, Schema<any>>;

  constructor(schema: Record<string, Schema<any>>) {
    super();
    this.schema = schema;
  }

  parse(input: unknown): T {
    if (typeof input !== 'object' || input === null) {
      throw new Error('Input is not an object');
    }

    const result = {} as T;
    for (const [key, fieldSchema] of Object.entries(this.schema)) {
      result[key as keyof T] = fieldSchema.parse((input as any)[key]);
    }
    return result;
  }
}

// 自动推断泛型的便捷函数
export function object<S extends Record<string, Schema<any>>>(
  schema: S
): ObjectSchema<InferSchema<S>> {
  return new ObjectSchema(schema);
}

测试效果

const userSchema = object({
  name: string(),
  age: number(),
  email: string()
});

// TypeScript会自动推断userSchema的类型为ObjectSchema<{
//   name: string;
//   age: number;
//   email: string;
// }>

const user = userSchema.parse({ name: "Alice", age: 30, email: "alice@example.com" });
// user的类型自动推断为上述对象类型,无需手动定义

4. 扩展到其他类型(比如数组)

用同样的思路扩展数组类型:

class ArraySchema<T> extends Schema<T[]> {
  private readonly itemSchema: Schema<T>;

  constructor(itemSchema: Schema<T>) {
    super();
    this.itemSchema = itemSchema;
  }

  parse(input: unknown): T[] {
    if (!Array.isArray(input)) throw new Error('Input is not an array');
    return input.map(item => this.itemSchema.parse(item));
  }
}

export function array<T>(itemSchema: Schema<T>): ArraySchema<T> {
  return new ArraySchema(itemSchema);
}

// 使用示例
const stringListSchema = array(string());
const list = stringListSchema.parse(["a", "b", "c"]);
// list的类型自动推断为string[]

关键原理总结

  • 每个Schema实例通过泛型T与编译时类型绑定,这是运行时与编译时的连接点。
  • InferSchema这类工具类型利用TypeScript的infer关键字,从Schema实例中提取出对应的泛型类型。
  • 便捷创建函数(如object)依靠TypeScript的自动泛型推断能力,从输入的Schema结构自动推导最终的编译时类型,无需手动指定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 06:27:44