如何根据Joi Schema中字段值动态添加额外字段?
如何在Joi中根据字段值动态添加额外字段
问题场景
现有一个Joi Schema,可根据type字段的值动态设置value的类型,运行正常:
Joi.object({ name: Joi.string().required(), params: Joi.array().items(Joi.object({ type: Joi.string().valid("number", "string", "boolean").required(), key: Joi.string().required(), value: Joi.when("type", { is: "number", then: Joi.number() }).when("type", { is: "string", then: Joi.string() }).when("type", { is: "boolean", then: Joi.boolean() }).allow(null).default(null) })) })
现在需要实现:
- 当
type="string"时,为对象添加default字段(带默认值) - 当
type="number"时,为对象添加min和max字段(带默认值)
尝试了以下代码,但未生效,既无报错也未生成预期的默认字段:
Joi.object({ name: Joi.string().required(), params: Joi.array().items(Joi.object({ key: Joi.string().required(), value: Joi.when("type", { is: "number", then: Joi.number() }).when("type", { is: "string", then: Joi.string() }).when("type", { is: "boolean", then: Joi.boolean() }).allow(null).default(null), type: Joi.string().valid("number", "string", "boolean").required(), }).when("type", { is: "number", then: Joi.object({ min: Joi.number().default(0), max: Joi.number().default(100) }) }).when("type", { is: "string", then: Joi.object({ default: Joi.string().default("default-value"), }) })) })
正确实现方式
要根据其他字段值动态添加字段,核心是在对象内部使用.when()定义条件字段,或结合.concat()合并条件对象。推荐两种写法:
方法一:对象内部直接定义动态字段
把需要动态添加的字段用.when直接写在顶层对象中,Joi会根据type的值自动启用对应字段:
Joi.object({ name: Joi.string().required(), params: Joi.array().items( Joi.object({ key: Joi.string().required(), type: Joi.string().valid("number", "string", "boolean").required(), value: Joi.when("type", { is: "number", then: Joi.number() }).when("type", { is: "string", then: Joi.string() }).when("type", { is: "boolean", then: Joi.boolean() }).allow(null).default(null), // string类型专属字段:仅type为string时生效 default: Joi.when("type", { is: "string", then: Joi.string().default("default-value"), otherwise: Joi.forbidden() // 非string类型禁止该字段 }), // number类型专属字段:仅type为number时生效 min: Joi.when("type", { is: "number", then: Joi.number().required().default(0), otherwise: Joi.forbidden() }), max: Joi.when("type", { is: "number", then: Joi.number().required().default(100), otherwise: Joi.forbidden() }) }) ) })
方法二:用.concat()合并模块化Schema
如果动态字段较多,可将不同类型的字段封装为独立Schema,再通过.when和.concat合并:
// 基础字段Schema const baseParamSchema = Joi.object({ key: Joi.string().required(), type: Joi.string().valid("number", "string", "boolean").required(), value: Joi.when("type", { is: "number", then: Joi.number() }).when("type", { is: "string", then: Joi.string() }).when("type", { is: "boolean", then: Joi.boolean() }).allow(null).default(null) }); // number类型专属字段Schema const numberParamSchema = Joi.object({ min: Joi.number().required().default(0), max: Joi.number().required().default(100) }); // string类型专属字段Schema const stringParamSchema = Joi.object({ default: Joi.string().default("default-value") }); // 最终组合Schema const schema = Joi.object({ name: Joi.string().required(), params: Joi.array().items( baseParamSchema .when("type", { is: "number", then: Joi.object().concat(numberParamSchema) }) .when("type", { is: "string", then: Joi.object().concat(stringParamSchema) }) ) });
关键说明
- 原代码失效原因:在对象外部调用
.when时,Joi不会自动将条件对象的字段合并到原对象中,必须显式使用.concat或在对象内部定义条件字段。 otherwise: Joi.forbidden()可确保非对应类型时,动态字段无法被提交,避免脏数据。- 两种方法各有适用场景:方法一适合字段较少的情况,方法二更适合字段较多、需要模块化管理的场景。
内容的提问来源于stack exchange,提问作者Marc
相关产品推荐
相关产品推荐

