TypeScript如何检查对象是否包含类型定义的全部属性
问题根源
TypeScript 的静态类型仅在编译阶段生效,编译完成后所有类型信息会被完全擦除,运行时不存在Command这类自定义类型的任何元数据,因此不可能直接把类型作为参数传给Object.keys()遍历属性做校验。
要实现无冗余的类型守卫,核心思路是维护一份和类型定义强绑定的运行时校验Schema,同时通过TS的类型检查保证Schema和类型定义始终同步,避免手动逐行写typeof判断的冗余,也不会出现类型更新后校验逻辑漏改的问题。
实现方案
首先写一个通用的结构校验工具函数,复用类型判断逻辑,避免每个类型都重复写判断链:
// 映射typeof操作符的返回值和对应TS类型 type TypeofMap = { string: string; number: number; boolean: boolean; function: Function; object: object | null; undefined: undefined; symbol: symbol; bigint: bigint; }; /** * 通用对象结构校验 * @param target 待校验值 * @param schema 属性名 -> 期望typeof返回值 的映射表 */ function checkShape<T extends Record<keyof T, keyof TypeofMap>>( target: unknown, schema: T ): target is { [K in keyof T]: TypeofMap[T[K]] } { // 先排除非对象、null的非法输入 if (typeof target !== 'object' || target === null) return false; // 遍历schema做批量判断,不用逐行写&&链式判断 return Object.entries(schema).every(([key, expectType]) => { return Object.prototype.hasOwnProperty.call(target, key) && typeof (target as Record<string, unknown>)[key] === expectType; }); }
针对Command类型,只需要定义对应的校验Schema,同时加一层编译时校验,保证Schema覆盖Command的所有必填属性,只要后续Command类型新增/删除属性,编译阶段就会直接报错,强制同步校验逻辑:
// Command类型定义 type Command<DataPerGuildType> = { name: string; execute(message: Message, guildData: DataPerGuildType, args: string[]): Promise<void>; }; // 运行时校验Schema,新增属性只要在这里加一行即可 const commandSchema = { name: 'string', execute: 'function', } as const; // 编译时校验:保证Schema和Command类型的必填属性完全对齐 type ValidateSchema<S> = S extends Record<keyof Command<unknown>, keyof TypeofMap> ? true : false; // 如果Schema漏了Command的属性,这行编译直接报错 const _schemaValid: ValidateSchema<typeof commandSchema> = true; // 最终的类型守卫 const isCommand = <GuildDataType>( toCheck: unknown, ): toCheck is Command<GuildDataType> => { if (!checkShape(toCheck, commandSchema)) return false; // 如果需要更严格校验execute的返回值是Promise,可以在这里加额外判断 // 注意:运行时无法校验execute的参数类型、泛型GuildDataType的具体结构,这是TS类型擦除的特性决定的 return true; };
注意事项
- 不要把待校验参数的类型标注为
object,应该用unknown,前者无法安全做索引访问,也无法提前过滤null这类typeof返回'object'的非法值 - 受限于TS类型擦除的设计,运行时无法校验泛型参数
GuildDataType的结构,也无法校验execute方法的参数、返回值类型是否完全匹配,如果需要强校验,只能在实际调用execute的逻辑里补充运行时检查 - 校验属性存在性时用
Object.prototype.hasOwnProperty.call(target, key)而不是直接调用target.hasOwnProperty,避免对象原型被篡改导致的判断错误
内容的提问来源于stack exchange,提问作者skeku
相关产品推荐
相关产品推荐

