You need to enable JavaScript to run this app. Play Framework Scala 如何在JSON自动映射上叠加自定义字段验证
优惠活动
大模型
产品
解决方案
定价
更多

Play Framework Scala 如何在JSON自动映射上叠加自定义字段验证

问题本质

你之前使用composeWith实现校验失败的核心原因是:composeWith的作用是衔接两个同JSON层级的Reads做类型转换,你把针对email单字段的Reads和整个UserDto根级对象的Reads直接拼接,两者的输入JSON层级完全不匹配,自然只会返回单字段的校验结果,无法触发整个UserDto的正常映射流程。

实现叠加校验完全不需要放弃自动生成的基础Reads,只需要在现有自动映射的基础上,分别嵌入单字段规则校验、跨字段规则校验即可,不会破坏原有字段映射逻辑。


实现步骤

1. 保留原有基础映射逻辑

你的Case Class和自动生成的读写代码不需要调整:

import play.api.libs.json._
import play.api.libs.json.Reads._

case class UserDto(
  id: Option[Int], 
  email: String, 
  username: String, 
  password: Option[String], 
  confirmedPassword: Option[String]
)

// 保留原有自动生成的读写逻辑
val baseUserDtoReads: Reads[UserDto] = Json.reads[UserDto]
val userDtoWrites: OWrites[UserDto] = Json.writes[UserDto]

2. 追加自定义校验规则

利用Play Reads提供的filter方法,在基础Reads成功解析出UserDto实例后,追加两层校验:

  • 单字段校验:调用Play内置的email校验器判断邮箱格式,无需手写正则
  • 跨字段校验:对比两个密码字段的输入值是否一致
val validatedUserDtoReads: Reads[UserDto] = baseUserDtoReads
  // 校验邮箱格式
  .filter(JsonValidationError("邮箱格式不合法")) { userDto =>
    email.predicate(userDto.email)
  }
  // 校验两次密码一致性
  .filter(JsonValidationError("两次输入的密码不一致")) { userDto =>
    (userDto.password, userDto.confirmedPassword) match {
      case (Some(pwd), Some(confirmPwd)) => pwd == confirmPwd
      // 可根据业务补充其他分支逻辑
      case _ => true
    }
  }

如果需要把错误信息直接挂载到对应的JSON路径下(方便前端定位错误输入框),可以手动组合带校验的字段Reads,替代自动生成的基础Reads:

val fineGrainedValidatedReads: Reads[UserDto] = (
  (JsPath \ "id").readNullable[Int] and
  // 字段级校验,错误自动挂载到/email路径
  (JsPath \ "email").read[String](email) and
  (JsPath \ "username").read[String] and
  (JsPath \ "password").readNullable[String] and
  (JsPath \ "confirmedPassword").readNullable[String]
)(UserDto.apply _)
  // 跨字段校验仍挂载在对象层面
  .filter(JsonValidationError("两次输入的密码不一致", "/password", "/confirmedPassword")) { userDto =>
    userDto.password.zip(userDto.confirmedPassword).exists { case (p1, p2) => p1 == p2 }
  }

3. 接口层接入校验逻辑

在Controller中直接使用带校验的Reads处理请求:

import play.api.mvc.{AbstractController, ControllerComponents}

class UserController(cc: ControllerComponents) extends AbstractController(cc) {
  def register() = Action(parse.json) { implicit req =>
    req.body.validate[UserDto](validatedUserDtoReads) match {
      case JsSuccess(user, _) =>
        // 校验通过,执行业务入库逻辑
        Ok(s"用户 ${user.username} 注册成功")
      case JsError(errors) =>
        // 校验失败,返回400状态码与结构化错误信息
        BadRequest(JsError.toJson(errors))
    }
  }
}

实际运行效果
  • 提交合法注册JSON(邮箱格式合规、两次密码一致)时,会正常映射为对应UserDto实例,进入业务流程
  • 邮箱格式非法时,直接返回400错误,附带“邮箱格式不合法”提示
  • 两次密码输入不一致时,直接返回400错误,附带“两次输入的密码不一致”提示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.10 04:36:07