如何扩展JOI Schema并保持TypeScript类型安全性?
如何在TypeScript中类型安全地扩展Joi Schema(继承接口场景)
问题场景
现有基础的Login接口和对应的Joi Schema:
import Joi from "joi"; interface Login { password: string; token?: string; } const LoginSchema = Joi.object<Login>() .keys({ password: Joi.string().optional().empty(null), token: Joi.string().optional().empty(null), }) .or("password", "token");
同时存在多个继承自Login的接口,例如:
interface User extends Login { username: string; } interface Bot extends Login { appId: string; }
需要为这些新接口扩展LoginSchema,但直接使用LoginSchema.keys({ appId: Joi.string().required() })会触发TypeScript类型报错(因为appId不属于Login接口),且不希望将登录凭证嵌套在子接口的字段中。
可行解决方案
方案1:使用Joi的concat()方法(推荐)
利用Joi内置的concat()方法合并基础Schema和新增字段的Schema,同时通过泛型指定最终的接口类型,既保留基础Schema的所有验证规则,又保证类型安全:
// 扩展User对应的Schema const UserSchema = Joi.object<User>() .keys({ username: Joi.string().required() }) .concat(LoginSchema); // 扩展Bot对应的Schema const BotSchema = Joi.object<Bot>() .keys({ appId: Joi.string().required() }) .concat(LoginSchema);
concat()会将LoginSchema的字段规则、验证约束(比如.or("password", "token"))全部合并到新Schema中,同时Joi.object<User>()会验证新增字段与User接口的匹配性,完全符合类型安全要求。
方案2:创建通用扩展工具函数
如果需要频繁扩展多个继承接口,可以封装一个工具函数,简化重复操作:
/** * 扩展基础Login Schema * @param baseSchema 基础Login Schema * @param extraFields 新增字段的Schema定义 * @returns 扩展后的Schema,类型为指定的T */ function extendLoginSchema<T extends Login>( baseSchema: Joi.ObjectSchema<Login>, extraFields: Joi.SchemaMap<Omit<T, keyof Login>> ): Joi.ObjectSchema<T> { return baseSchema.keys(extraFields) as Joi.ObjectSchema<T>; } // 使用示例 const UserSchema = extendLoginSchema<User>(LoginSchema, { username: Joi.string().required() }); const BotSchema = extendLoginSchema<Bot>(LoginSchema, { appId: Joi.string().required() });
通过Omit<T, keyof Login>提取新增字段的类型,再通过类型断言将扩展后的Schema转为目标类型Joi.ObjectSchema<T>,既规避了直接keys()的类型报错,又保证了最终Schema的类型正确性。
方案3:手动合并字段定义(不推荐)
如果需要更精细的控制,可以手动提取基础Schema的字段定义,再合并新增字段,但需要手动复制基础Schema的验证约束(比如.or):
const UserSchema = Joi.object<User>({ username: Joi.string().required(), // 提取LoginSchema的字段定义 ...LoginSchema.describe().keys as Record<keyof Login, Joi.Schema> }) .or("password", "token"); // 手动保留基础Schema的or规则
此方法需要手动维护基础Schema的额外验证规则,不如concat()便捷,仅适合特殊场景使用。
内容的提问来源于stack exchange,提问作者Jallrich
相关产品推荐
相关产品推荐

