You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Scala Cats校验类的非空字段组合有效性

嘿,这个需求用Scala Cats来实现其实挺顺畅的,我来给你拆解一下思路和具体代码,帮你搞定这个校验逻辑!

首先,我们的核心目标是:只要Test实例满足任意一组指定的字段非空组合,校验就通过;如果所有组合都不满足,就把所有失败的组合信息返回给用户。Cats的Validated类型正好适合这种需要错误累积的场景,同时我们可以利用Scala的元组和Option特性来简化字段非空检查。

步骤1:导入必要的Cats依赖和定义Test类

首先确保你已经引入了Cats核心库,然后定义你的样例类:

import cats.data.Validated
import cats.syntax.all._

case class Test(a: Option[String], b: Option[String], c: Option[String], d: Option[String], e: Option[Double], f: Option[Double])

步骤2:为每个有效组合编写校验函数

我们为每个指定的字段组合单独写一个校验逻辑:用tupled把多个Option组合成一个Option元组——只有当所有字段都是Some时,tupled才会返回Some,否则是None。然后用toValid把这个Option转换成Validated,失败时返回对应的错误信息:

object TestValidator {
  // 校验组合1: a、b、c全部非空
  private def validateGroup1(test: Test): Validated[String, Unit] =
    (test.a, test.b, test.c).tupled.toValid("组合a,b,c未全部非空").void

  // 校验组合2: a、d、e全部非空
  private def validateGroup2(test: Test): Validated[String, Unit] =
    (test.a, test.d, test.e).tupled.toValid("组合a,d,e未全部非空").void

  // 校验组合3: a、f全部非空
  private def validateGroup3(test: Test): Validated[String, Unit] =
    (test.a, test.f).tupled.toValid("组合a,f未全部非空").void

步骤3:组合所有校验逻辑,实现“至少满足一组”的规则

接下来把三个校验函数的结果组合起来:只要有一个校验通过,整体就通过;如果所有都失败,就收集所有错误信息。这里用foldLeft遍历所有校验结果,遇到Valid就直接返回(提前终止,不用检查剩下的组合),否则累加错误:

// 主校验函数:至少满足一个组合则通过,否则返回所有失败信息
  def validate(test: Test): Validated[List[String], Unit] = {
    val validations = List(validateGroup1(test), validateGroup2(test), validateGroup3(test))
    
    validations.foldLeft(Invalid(List.empty[String]): Validated[List[String], Unit]) {
      case (_, valid @ Valid(_)) => valid // 找到一个通过的,直接返回
      case (Invalid(errs), Invalid(err)) => Invalid(errs :+ err) // 收集失败信息
      case (acc, _) => acc
    }
  }
}

测试一下效果

我们可以写几个测试用例验证逻辑:

// 测试用例1:满足组合a,b,c → 校验通过
val test1 = Test(Some("a"), Some("b"), Some("c"), None, None, None)
println(TestValidator.validate(test1)) // 输出: Valid(())

// 测试用例2:满足组合a,f → 校验通过
val test2 = Test(Some("a"), None, None, None, None, Some(1.0))
println(TestValidator.validate(test2)) // 输出: Valid(())

// 测试用例3:所有组合都不满足 → 返回所有失败信息
val test3 = Test(None, Some("b"), Some("c"), Some("d"), None, None)
println(TestValidator.validate(test3)) // 输出: Invalid(List(组合a,b,c未全部非空, 组合a,d,e未全部非空, 组合a,f未全部非空))

// 测试用例4:满足多个组合 → 校验通过
val test4 = Test(Some("a"), Some("b"), Some("c"), Some("d"), Some(2.0), Some(3.0))
println(TestValidator.validate(test4)) // 输出: Valid(())

额外优化:类型安全的错误信息

如果不想用字符串作为错误信息,也可以定义自定义的错误枚举,让校验更类型安全:

sealed trait ValidationError
case object Group1Failed extends ValidationError
case object Group2Failed extends ValidationError
case object Group3Failed extends ValidationError

// 然后修改校验函数的返回类型:
private def validateGroup1(test: Test): Validated[ValidationError, Unit] =
  (test.a, test.b, test.c).tupled.toValid(Group1Failed).void

// 主函数返回Validated[List[ValidationError], Unit],这样错误信息更清晰且类型安全

这样整个逻辑就清晰又简洁了,完全利用了Cats的Validated和Scala的元组特性来实现需求~

内容的提问来源于stack exchange,提问作者cardosojc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:33:11