【问题标题】:Play Framework JSON automated mapping with custom validation带有自定义验证的 Play Framework JSON 自动映射
【发布时间】:2021-01-19 22:36:38
【问题描述】:

大家好,我是 Scala 世界的新手,我想使用 Play Framework 创建一个简单的 REST API。我正在处理 JSON 到案例类的映射,我可能有一个愚蠢的问题。

我有一个代表UserDto 的案例类(它用于用户注册,并获取有关用户的一些基本信息)

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

正如 Play Framework 文档中所说,有多种方法可以将其映射到 JSON,我正在查看 automated mapping part

我的做法是

val userDtoWrites: OWrites[UserDto] = Json.writes[UserDto]
val userDtoReads: Reads[UserDto] = Json.reads[UserDto]

现在我想为我的 UserDto 添加一些验证,例如,检查电子邮件是否正确。

所以我的问题是,是否有可能创建一个仅检查电子邮件字段的 Read 并将其添加到自动生成的 Read 中?

我用 composeWith 尝试过,但在运行第二个 Read 后,它只返回电子邮件部分。

val checkEmail: String = (JsPath \ "email").read[String](email)
val checkEmailJsString: JsString = checkEmail.map(s => JsString(s))
val newRead = userDtoReads.composeWith(checkEmailJsString))

例如,当用户想要注册时:

{"email": "user@email.com", "username": "user","password": "password1234", "confirmedPassword": "password1234"} 

这应该映射到:

UserDto(None, user@email.com, user, password1234, password1234)

如果一切都是正确的。如果密码不匹配或电子邮件格式错误,例如应该返回 400。

【问题讨论】:

  • 为什么要采用这种方法?您可以在创建类时添加验证:case class UserDto (...) { checkemail(email) }。这样它将被检查到这个类的所有实例,而不仅仅是从 json 创建
  • 我明白你的意思,但我的想法是将此 dto 仅用作 http 层的一部分(这意味着我会将 JSON 反序列化到其中,并将数据从 User 模型复制到其中,这应该已经检查过)。而且我想使用 Play 的内置电子邮件检查功能,这样我就不必编写自己的正则表达式了。
  • 我认为有一个误解,没有自动电子邮件检查,但作为 Play 验证的一部分,这里有一个 playframework.com/documentation/2.8.x/…。我想将它与自动读取结合起来,但似乎无法结合两个读取不同字段的读取。
  • val userDtoReads: Reads[UserDto] = Json.reads[UserDto] 和这个val checkEmail: String = (JsPath \ "email").read[String](email)
  • 也许添加一个 json 输入,所需的实例输出将有助于更好地回答这个问题。

标签: scala play-json


【解决方案1】:

如果您想拥有只有有效电子邮件的 UserDto 实例,那么您应该考虑使用 Refined 类型(例如使用 https://github.com/avdv/play-json-refined 来提供对 Play JSON 的支持)。

import eu.timepit.refined._
import eu.timepit.refined.auto._
import eu.timepit.refined.api._
import play.api.libs.json._
import de.cbley.refined.play.json._

type EmailPattern = "^\S+@\S+\.\S+$"       // Scala 2.13 version
type EmailPattern = W.`"^\S+@\S+\.\S+$"`.T // before 2.13

type Email = String Refined string.MatchesRegex[EmailPattern]

case class UserDto(
  id: Option[Int],
  email: Email, // instead of plain String
  username: String,
  password: Option[String],
  confirmedPassword: Option[String]
)
object UserDto {
  implicit val userDtoWrites: OWrites[UserDto] = Json.writes[UserDto]
  implicit val userDtoReads: Reads[UserDto] = Json.reads[UserDto]
}

【讨论】:

  • 嗯,是的,因为 Scala 是一种类型化语言,我想这是最有意义的。我去看看
【解决方案2】:

如果不想使用其他库,可以实现自己的Reads[UserDto]

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

implicit val reads: Reads[UserDto] = (
  (__ \ "id").readNullable[Int] and
    (__ \ "email").read[String].filter(JsonValidationError("email is not valid"))(isValid) and
    (__ \ "username").read[String] and
    (__ \ "password").readNullable[String] and
    (__ \ "confirmedPassword").readNullable[String]
  ) (UserDto).filter(
  JsonValidationError("password and confirmedPassword must be equal")
) { userDto =>
  userDto.password == userDto.confirmedPassword
}

然后就可以使用了:

val successfulJsonString = """{"email": "user@email.com", "username": "user", "password": "password1234", "confirmedPassword": "password1234"}"""
val emailJsonString = """{"email": "user", "username": "user","password": "password1234", "confirmedPassword": "password1234"}"""
val passwordsJsonString = """{"email": "user@email.com", "username": "user","password": "password1234", "confirmedPassword": "1234"}"""

Json.parse(successfulJsonString).validate[UserDto].fold(println, println)
Json.parse(emailJsonString).validateOpt[UserDto].fold(println, println)
Json.parse(passwordsJsonString).validate[UserDto].fold(println, println)

并得到输出:

UserDto(None,user@email.com,user,Some(password1234),Some(password1234))
List((/email,List(JsonValidationError(List(email is not valid),List()))))
List((,List(JsonValidationError(List(field1 and field2 must be equal),List()))))

我参考了Scala play json combinators for validating equalityhow to add custom ValidationError in Json Reads in PlayFramework 来构建该解决方案。

代码在Scastie 运行。

【讨论】:

  • 我认为您的回答对 OP 没有用,因为他们专门询问了自动生成,他们知道手动滚动编解码器。
  • @MateuszKubuszok,您可能是对的。但据我了解,OP 希望在 json 有效性之上进行额外的验证。如评论stackoverflow.com/questions/65616691/…
猜你喜欢
  • 2015-04-29
  • 2014-12-06
  • 1970-01-01
  • 1970-01-01
  • 2013-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多