如何使用Vavr Validation的combine方法实现多层嵌套对象校验
Vavr多层嵌套对象校验实现方案
问题根因说明
你遇到的两个问题的核心原因如下:
- 类型不匹配:Vavr的
Validation.combine要求所有传入的校验实例的错误侧类型必须完全一致,如果你的基础字段校验方法(比如validateName)之前返回的是单个ConstraintViolation而非Seq<ConstraintViolation>,就会和validateAddress返回的错误类型不匹配。 - 嵌套校验缺失:多层嵌套对象校验需要额外处理字段路径拼接,以及内层校验逻辑的接入,避免错误字段路径歧义。
实现步骤
1. 统一所有校验方法的错误返回类型
所有校验方法的失败侧统一返回Seq<ConstraintViolation>,单个字段校验失败也要封装为单元素Seq,保证combine时类型对齐。示例如下:
// 构造ConstraintViolation的工具方法,可根据自身业务实现调整 private static ConstraintViolation buildViolation(String fieldPath, String message) { return new ConstraintViolation(fieldPath, message); } // 姓名校验示例,错误侧统一返回Seq<ConstraintViolation> public static Validation<Seq<ConstraintViolation>, String> validateName(String fieldPath, String name) { if (name == null || name.isBlank()) { return Validation.invalid(Seq.of(buildViolation(fieldPath, "姓名不能为空"))); } return Validation.valid(name); } // validateEmail、validatePhone按相同规则改造,错误侧统一返回Seq<ConstraintViolation>
2. 实现AddrDetails层级校验
新增AddrDetails的校验方法,支持传入父路径前缀,拼接完整的错误字段路径:
public static Validation<Seq<ConstraintViolation>, Person.Address.AddrDetails> validateAddrDetails(String parentPath, Person.Address.AddrDetails details) { // 非空校验,如果details是可选字段可去掉该判断,直接返回Validation.valid(null) if (details == null) { return Validation.invalid(Seq.of(buildViolation(parentPath, "地址详情不能为空"))); } String pathPrefix = parentPath + "."; return Validation.combine( // 校验state字段 validateNotBlank(pathPrefix + "state", details.getState()), // 校验country字段 validateNotBlank(pathPrefix + "country", details.getCountry()) ).ap(Person.Address.AddrDetails::new); }
3. 改造Address校验方法,接入嵌套的AddrDetails校验
public static Validation<Seq<ConstraintViolation>, Person.Address> validateAddress(String parentPath, Person.Address request) { // 非空校验,如果address是可选字段可去掉该判断,直接返回Validation.valid(null) if (request == null) { return Validation.invalid(Seq.of(buildViolation(parentPath, "地址不能为空"))); } String pathPrefix = parentPath + "."; return Validation.combine( validateNotBlank(pathPrefix + "city", request.getCity()), validateNotBlank(pathPrefix + "pincode", request.getPincode()), validateNotBlank(pathPrefix + "street", request.getStreet()), // 嵌套校验AddrDetails,传入父路径 validateAddrDetails(pathPrefix + "details", request.getDetails()) ).ap(Person.Address::new); }
4. 修正Person校验方法,完成类型对齐
public static Validation<Seq<ConstraintViolation>, PersonDetailsModel> validatePerson(PersonDetailsRequest request) { return Validation.combine( validateName("name", request.getName()), validateEmail("email", request.getEmail()), validatePhone("phone", request.getPhone()), // 传入address的父路径 validateAddress("address", request.getAddress()) ).ap(PersonDetailsModel::new); }
核心注意点
- 所有校验方法的错误类型完全一致,都是
Seq<ConstraintViolation>,Vavr的combine会自动把所有校验项的错误合并为一个Seq返回,不会丢失任何校验失败信息 - 嵌套校验时传递父路径前缀,最终返回的错误字段路径是完整层级,比如
address.details.state,方便前端定位错误字段 - 如果嵌套对象是可选的,只需要在对应校验方法里判空时返回
Validation.valid(null)即可,不需要中断校验流程
内容的提问来源于stack exchange,提问作者raaeusagvc
相关产品推荐
相关产品推荐

