TypeScript Source:Write your own Zod Github: GitHub - zackradisic/write-your-own-zod: Write your own Zod from scratch 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