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
相关产品推荐
相关产品推荐

