TypeScript

type ZodType = ZodUnknown | ZodNumber | ZodString | ZodArray<ZodType> | ZodObject<Record<string, ZodType>>;
 
/**
 * 使用可区分联合类型定义类型
 * 使用 interface 而不使用 type 的原因是因为使用 type 的话,ZodType 会报错 “类型别名“ZodType”循环引用自身”
 */
interface ZodUnknown { 
  type: 'unknown',
  parse(val: unknown): unknown;
};
interface ZodString { 
  type: 'string',
  parse(val: unknown): string;
};
interface ZodNumber { 
  type: 'number',
  parse(val: unknown): number;
};
interface ZodArray<T extends ZodType> { 
  type: 'array', 
  element: T,
  parse(val: unknown): Array<Infer<T>>
};
interface ZodObject<T extends Record<string, ZodType>> {
  type: 'object', 
  fields: T,
  parse(val: unknown): InferZodObject<ZodObject<T>>
}
 
/**
 * [key in keyof T['fields']]: 获取 T['fields'] 对象的键
 * Infer<T['fields'][key]>:获取T['fields'] 对象的值,并且通过 Infer 获取值的类型
 */
type InferZodObject<T extends ZodObject<Record<string, ZodType>>> = {
  [key in keyof T['fields']]: Infer<T['fields'][key]>
}
 
type Infer<T extends ZodType> = T extends ZodUnknown 
  ? unknown
  : T extends ZodString
  ? string
  : T extends ZodNumber
  ? number
  : T extends ZodArray<infer E> // infer E 类似JS的对象结构,E表示了 Array的元素类型,也就是 ZodType
  ? Array<Infer<E>>
  : T extends ZodObject<Record<string, ZodType>>
  ? InferZodObject<T>
  : 'invalid type';
 
// type A = Infer<ZodArray<ZodNumber>> // number[]
 
// type O = {
//   type: 'object';
//   fields: {
//     name: ZodString;
//     age: ZodNumber;
//   }
// }
// type OType = Infer<O>;  // type OType = { name: string; age: number;}
 
const string = ():ZodString => ({ 
  type: 'string',
  parse(val): string {
    if (typeof val !== 'string') {
      throw new Error('Not a string')
    }
    return val;
  }
});
const number = ():ZodNumber => ({ 
  type: 'number',
  parse(val): number {
    if (typeof val !== 'number') {
      throw new Error('Not a number');
    }
    return val;
  }
});
const unknown = ():ZodUnknown => ({ 
  type: 'unknown',
  parse(val): unknown {
    return val;
  }
});
const array = <T extends ZodType>(element: T): ZodArray<T> => ({
  type: 'array',
  element,
  parse(val): Array<Infer<T>> {
    if (!Array.isArray(val)) {
      throw new Error("Not a Array")
    }
    val.forEach(v => this.element.parse(v));
 
    return val;
  }
});
const object = <T extends Record<string, ZodType>>(fields: T): ZodObject<T> => ({
  type: 'object',
  fields,
  parse(val): InferZodObject<ZodObject<T>> {
    if (typeof val !== 'object' || val === null) {
      throw new Error("Not a object")
    }
 
    const recordVal = val as Record<string, unknown>;
 
    Object.entries(this.fields).forEach(([k ,v]) => v.parse(recordVal[k]));
 
    return val as InferZodObject<ZodObject<T>>
  }
});
 
const z = {
  string,
  number,
  unknown,
  array,
  object
};
 
// test
const Monster = z.object({
  age: z.number(),
  name: z.string(),
  list: z.array(z.number()),
});
type MonsterType = Infer<typeof Monster>;
 
const monster:MonsterType  = {
  age: 20,
  name: 'L',
  list: [12]
};
 
// success
console.log(Monster.parse(monster));
// error
Monster.parse(null); // Error: Not a object
Monster.parse({
	age: 'Y'
}); // Error: Not a number