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

Kotlin使用Arrow验证超10字段时zip方法超限的优雅方案咨询

Kotlin Arrow库超过10个值的验证实现方案

在Kotlin中使用Arrow库做数据校验时,Validated类型内置的zip方法默认最多支持组合10个校验结果,新增第11个校验逻辑就会触发类型不匹配报错,现有实现代码如下:

fun CreateEventDTO.validate(): Validated<IncorrectInput, CreateEventDTO> =
  name.isEventNameValid()
    .zip(
      about.isAboutValid(),
      phone.isPhoneValid(),
      price.isPriceValid(),
      location.isLocationValid(),
      startDate.isStartDateValid(),
   // TODO add common validation for date
      // endDate.isEndDateValid(),
      status.isEventStatusValid(),
      access.isEventAccessValid(),
      category.isEventCategoryValid(),
      musicStyles.isMusicStyleValid()
    )
    { name, _, _, price, location, status, access, category, musicStyles ->
      CreateEventDTO(
        name = name,
        about = about,
        phone = phone,
        price = price,
        location = location,
        startDate = startDate,
        endDate = endDate,
        status = status,
        access = access,
        category = category,
        musicStyles = musicStyles
      )
    }
    .mapLeft(ApiError::IncorrectInput)

新增校验逻辑后抛出的类型报错如下:

Required:
Semigroup<TypeVariable(E)>
Found:
ValidatedNel<InvalidAbout, String?> /* = Validated<NonEmptyList<InvalidAbout>, String?> */

可选实现方案

嵌套zip组合

不需要额外扩展API,直接多层嵌套zip即可保留错误累积能力,嵌套后调用flatten()拍平多余的Validated包装层即可:

fun CreateEventDTO.validate(): Validated<IncorrectInput, CreateEventDTO> =
  name.isEventNameValid()
    .zip(
      about.isAboutValid(),
      phone.isPhoneValid(),
      price.isPriceValid(),
      location.isLocationValid(),
      startDate.isStartDateValid(),
      endDate.isEndDateValid(),
      status.isEventStatusValid(),
      access.isEventAccessValid(),
    ) { name, about, phone, price, location, startDate, endDate, status, access ->
      category.isEventCategoryValid()
        .zip(musicStyles.isMusicStyleValid()) { category, musicStyles ->
          CreateEventDTO(
            name = name,
            about = about,
            phone = phone,
            price = price,
            location = location,
            startDate = startDate,
            endDate = endDate,
            status = status,
            access = access,
            category = category,
            musicStyles = musicStyles
          )
        }
    }.flatten()
    .mapLeft(ApiError::IncorrectInput)

注意:嵌套时不要混用错误类型,保证所有校验分支的错误类型可以被Semigroup合并(比如统一用NonEmptyList<ValidationError>作为错误类型)。

Validated计算块(生产环境推荐)

直接使用Arrow提供的validated上下文构建器,通过bind()方法逐个绑定校验结果,没有参数数量限制,代码可读性更高:

fun CreateEventDTO.validate(): Validated<IncorrectInput, CreateEventDTO> =
  validated {
    val name = name.isEventNameValid().bind()
    val about = about.isAboutValid().bind()
    val phone = phone.isPhoneValid().bind()
    val price = price.isPriceValid().bind()
    val location = location.isLocationValid().bind()
    val startDate = startDate.isStartDateValid().bind()
    val endDate = endDate.isEndDateValid().bind()
    val status = status.isEventStatusValid().bind()
    val access = access.isEventAccessValid().bind()
    val category = category.isEventCategoryValid().bind()
    val musicStyles = musicStyles.isMusicStyleValid().bind()
    // 后续新增校验字段直接追加bind即可,无数量上限
    CreateEventDTO(
      name = name,
      about = about,
      phone = phone,
      price = price,
      location = location,
      startDate = startDate,
      endDate = endDate,
      status = status,
      access = access,
      category = category,
      musicStyles = musicStyles
    )
  }.mapLeft { ApiError.IncorrectInput(it) }

这个方案的优势:

  • 无参数数量限制,扩展字段成本极低
  • 字段赋值和校验结果一一对应,不会出现原zip写法中用_占位、误引用外部字段的问题
  • 完全保留zip的错误累积特性,所有校验错误会被一次性收集返回,不会提前终止

自定义高元数zip扩展

如果偏好zip的编码风格,可以参照Arrow内置zip的源码,自行扩展11参、12参甚至更高参数数量的zip扩展函数。但这种方式需要为每个元数单独写扩展,维护成本较高,不推荐优先使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 12:45:30