TypeScript中Schema校验后如何实现跨接口类型收窄
问题说明
需求是在使用schema对象对data对象做运行时校验后,能在TypeScript静态类型层面自动收窄data对应属性的类型,避免手动写类型断言。
核心前提要先明确:TypeScript的类型检查只在编译阶段生效,如果你把schema直接标注为宽泛的Schema接口,相当于主动抹掉了schema里各个字段的具体类型配置信息,TS无法获知每个字段预期的类型,自然不可能完成自动类型收窄。
可行实现方案
方案1:泛型改造+断言函数(对应你提到的思路的可行版本)
你提到的asserts关键字方案是完全可行的,核心是不要丢失schema的字面量类型信息,通过泛型从schema的结构推导出校验后data的类型:
interface Data { [key: string]: number | string | undefined; } // 建立schema type配置到TS类型的映射 type TypeMap = { string: string; number: number; }; // 泛型Schema类型,支持从传入的properties字面量结构推导 type Schema<T extends Record<string, { type: keyof TypeMap }>> = { properties: T; }; // 从Schema结构推导校验通过后的Data类型 type ValidatedData<T extends Schema<any>> = { [K in keyof T["properties"]]: TypeMap[T["properties"][K]["type"]]; } & Data; /** * 运行时校验函数,通过asserts关键字声明类型收窄效果 */ function validate<T extends Schema<any>>( data: Data, schema: T ): asserts data is ValidatedData<T> { // 实际运行时校验逻辑 for (const [key, rule] of Object.entries(schema.properties)) { if (!rule) continue; if (typeof data[key] !== rule.type) { throw new Error(`字段 ${key} 类型不匹配,预期为 ${rule.type}`); } } } // 注意:不要给schema标注宽泛的Schema类型,加as const保留字面量类型信息 const schema = { properties: { firstName: { type: "string", }, }, } as const; const data: Data = { firstName: "John", }; validate(data, schema); // 校验通过后,TS自动推导data.firstName为string类型 const firstName: string = data.firstName; // 无类型报错
你提到的把Schema改造成带getter的类的方案本质和这个思路一致,只要类上携带足够的泛型类型信息,就能实现同样的效果,但是自定义类的实现成本更高,没有特殊需求没必要这么做。
另外你示例里写的通过if (schema.properties.firstName?.type === "string")判断收窄的写法是无法生效的:TS的控制流分析默认只会收窄被判断的变量本身的类型,不会自动关联到独立的data变量的类型,必须通过类型谓词或者asserts断言函数显式声明两个变量的类型关联关系。
方案2:直接使用成熟的Schema校验库
这类运行时类型校验的需求已经有非常成熟的开源实现,不需要自己手写泛型和校验逻辑,这类库天生做了运行时校验和静态类型推导的绑定:
- 最常用的是
zod:零依赖,类型推导友好,API简洁,定义schema后可以直接推导出对应的TS类型,校验方法自带类型收窄 - 同类型替代还有
valibot(体积更小)、yup(生态成熟,v2+版本类型支持完善)
以zod为例的实现代码:
import { z } from "zod"; // 定义schema const userSchema = z.object({ firstName: z.string(), age: z.number() }); // 待校验数据,类型可以是unknown const data: unknown = { firstName: "John", age: 20 }; // 运行时校验,校验失败会抛错,校验通过后validData自动推导为对应类型 const validData = userSchema.parse(data); const firstName: string = validData.firstName; // 无类型报错 const age: number = validData.age; // 无类型报错
内容的提问来源于stack exchange,提问作者tavoyne
相关产品推荐
相关产品推荐

