跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

Joi 实现字段互斥且必填其一的验证规则

本文介绍如何使用 joi 构建互斥字段(如 valuea 与 valueb)的验证逻辑:二者有且仅有一个必须存在且非空,另一个必须完全缺失或为空字符串,确保业务数据的排他性约束。 本文介绍如何使用 joi 构建互斥字段(如 valuea 与 valueb)的验证逻辑:二者有且仅有一个必须存在且非空,另一个必须完全缺失或为空字符串,确保业务数据的排他性约束。 在构建表单、API 请求体或配置对象校验时,常遇到“二选一”型字段约束需求:例如 valueA 和 valueB 不能同时存在,也不能同时为空——必须 有且仅有一个提供有效值 。Joi 并未内置 xor() 或 mutuallyExclusive() 这类高阶语义方法(注意:Joi v17+ 的 xor() 仅支持字段 存在性互斥 ,不处理空字符串等“假值”场景),因此需结合 alternatives().try()、required() 与精细化的空值处理来精准建模。 以下是一个健壮、可复用的解决方案:
const Joi = require('joi'); const schema = Joi.alternatives().try( // 情况一:valueA 存在且非空,valueB 必须不存在或显式为空字符串(按业务需要可调整) Joi.object({ valueA: Joi.string() .pattern(/^\d+(,\d{1,2})?(\.\d{1,2})?$/) .required() .min(1), // 确保非空字符串(排除 '') valueB: Joi.string() .optional() // 允许缺失 .empty(''), // 若存在,则空字符串视为合法(可移除此行以禁止传空字符串) }), // 情况二:valueB 存在且非空,valueA 同理处理 Joi.object({ valueB: Joi.string() .pattern(/^\d+(,\d{1,2})?(\.\d{1,2})?$/) .required() .min(1), valueA: Joi.string() .optional() .empty(''), }) ).messages({ 'any.only': 'Exactly one of "valueA" or "valueB" must be provided with a valid numeric string.', });
✅ 关键设计说明 : 使用 Joi.alternatives().try() 显式定义两种合法结构,Joi 将按顺序尝试匹配,只要任一结构通过即视为整体有效; 每个子对象中, 必填字段(.required().min(1))确保其存在且非空 ,而另一字段设为 .optional().empty(''),允许缺失或传入空字符串(若业务要求“另一字段必须完全不可见”,可改为 .forbidden()); 正则 ^\d+(,\d{1,2})?(\.\d{1,2})?$ 支持整数、带千分位逗号的数字(如 1,234)、以及最多两位小数的浮点数(如 12.34),已适配常见金额/数值格式; .messages() 自定义错误提示,提升调试与用户反馈体验。 ⚠️ 注意事项 : 若输入对象同时包含 valueA 和 valueB(即使其中一个为空字符串),两个分支均会失败,触发 'any.only' 错误; Joi 默认将 null 视为缺失值,如需支持 null 作为有效“空态”,需额外调用 .valid(null) 并配合 .empty(null); 对于数组内多个对象的批量校验(如问题中提到的“数组 of objects”),只需将上述 schema 作为 Joi.array().items(schema) 的元素校验器即可无缝复用。 总结而言,alternatives().try() 是实现 Joi 中复杂条件逻辑(尤其是互斥、多态结构)的核心模式。它比依赖 .when() 的嵌套条件更清晰、更易测试,也避免了因空字符串、undefined、null 等边界值导致的验证漏洞。在实际项目中,建议将此类互斥规则封装为可复用的 Joi.extend() 自定义类型,进一步提升代码可维护性。

相关文章