【问题标题】:Validate json property with dependency of other property in Play 2.4验证 json 属性与 Play 2.4 中其他属性的依赖关系
【发布时间】:2016-03-19 16:06:11
【问题描述】:

我有

case class AclRuleScope(kind: String, value: String)

我想将Json 转换为AclRuleScope,但有限制:

type 可能只有:“默认”| “用户” | “组” | “域”

value 只能是电子邮件,如果类型是“用户”| "group",其他情况下还有一些字符串

我有 ReaderWriter 的对象,但我不明白如何在阅读 value 时获得 type 值:

object AclRuleScope {

  implicit val aclRuleScopeRead = (
    (__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)) and
      (__ \ "value").read[String](
          email keepAnd 
          filter(
              ValidationError("error.scope.value")
          )( ??? == JsString("user") || ??? == JsString("group")))
    )(this.apply _)

}

??? 中必须包含的内容

【问题讨论】:

    标签: json scala validation playframework-2.0


    【解决方案1】:

    JsConstraints#filter 具有以下签名

    def filter[A](otherwise: ValidationError)(p: A => Boolean)(implicit reads: Reads[A])
    

    当你写作时

    filter(ValidationError("error.scope.value"))(??? == JsString("user") || ??? == JsString("group")))
    

    代码(??? == JsString("user") || ??? == JsString("group")) 实际上是要过滤的第二个参数,因此它应该是A => Boolean 的谓词。此外,由于这是在email 读取为Reads[String] 之后应用的,因此您的实际AString,因此您应该删除JsString

    你能写出的最小改动是:

    implicit val aclRuleScopeRead = (
      (__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)) and
        (__ \ "value").read[String](
          email keepAnd
            filter(
              ValidationError("error.scope.value")
            )(x => x == "user" || x == "group"))
      ).tupled
    

    我强烈建议您将谓词提取到自己的方法中:

    def isValidEmail: (String) => Boolean = {
      x => x == "user" || x == "group"
    }
    

    把你的阅读写成

    implicit val aclRuleScopeRead = (
      (__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)) and
        (__ \ "value").read[String](
          email keepAnd filter(ValidationError("error.scope.value"))(isValidEmail))
      ).tupled
    

    你可以拥有更好的

    val validEmail = email keepAnd filter(ValidationError("error.scope.value"))(isValidEmail))
    

    然后写

    implicit val aclRuleScopeRead = (
      (__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)) and
        (__ \ "value").read[String](validEmail)
      ).tupled
    

    根据 cmets 的说明,如果类型是“用户”或“组”,您只想解析电子邮件,否则返回空字符串。

    答案接近this question中概述的解决方案

    value字段的读取首先需要检查type字段的值。类型字段的条件如下所示:

    (__ \ "type").read[String].filter(ValidationError("error.scope.value"))(isEmailType)

    其中 isEmailType 定义为

    def isEmailType: (String) => Boolean = { x => x == "user" || x == "group" }
    

    如果类型是usergroup,这将返回一个读取JsSuccess,否则返回一个JsError。从 cmets 中我们知道,如果类型不是usergroup,我们应该返回空字符串,读取可以变成:

    (__ \ "type").read[String]
                .filter(ValidationError("error.scope.value"))(isEmailType)
                .orElse Reads.pure("")
    

    这是安全的,永远不会返回JsError。这很好,因为有一个专用的reads 来强制对type 进行验证,我们当前正在操作的读取只是作为value 验证的一部分。 现在我们需要将读取更改为解析value,如果解析是JsSuccess

    (__ \ "type").read[String]
                .filter(ValidationError("error.scope.value"))(isEmailType)
                .flatMap(_ => (__ \ "value").read[String](email))
                .orElse Reads.pure("")
    

    使用 flatMap,如果 type 读取成功,我们将 type 读取替换为 value 上的正确读取。

    【讨论】:

    • validEmail 将测试 (__\"value")?验证总是会出错,因为“用户”|| “组”不是有效的电子邮件。仅当 (__\"type") 等于 "user" || 时,我希望 test (__\"value") 是电子邮件“团体”。我需要 ??? 作为来自 (__\"type") 的值
    • 哦,好吧,对不起,我想我明白你的意思了,它会更长 :) 如果类型是别的东西,那么 value 会发生什么,只是一个字符串?
    • 对于 'default' 类型 - 空字符串,对于 'domain' 类型 - (__\"value") 值。其他受 (__\"type") read 限制的类型。
    【解决方案2】:

    感谢@Jean,您为我指明了正确的方向。

    flatMapfilter 帮助了我,我有

    case class AclRuleScope(kind: String, value: Option[String])
    
    val aclRuleScopeRead = (
        (__ \ "type").read[String] and
        (__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)).flatMap {
          case t if "user".equals(t) || "group".equals(t) =>
            (__ \ "value").readNullable[String](email)
              .filter(ValidationError(s"error.acl.scope.value omitted for '$t' type"))(_.isDefined)
              .filter(ValidationError(s"error.acl.scope.value not defained for '$t' type"))(_.exists(_.nonEmpty))
          case "domain" =>
            (__ \ "value").readNullable[String]
              .filter(ValidationError("error.acl.scope.value omitted for 'domain' type"))(_.isDefined)
              .filter(ValidationError("error.acl.scope.value not defained for 'domain' type"))(_.exists(_.nonEmpty))
          case "default" =>
            (__ \ "value").readNullable[String]
              .filter(ValidationError("error.acl.scope.value must be omitted for 'default' type"))(_.isEmpty)
        }
    )(AclRuleScope.apply _)
    

    【讨论】:

      猜你喜欢
      • 2011-08-20
      • 1970-01-01
      • 2017-12-16
      • 2016-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-17
      • 2011-09-09
      相关资源
      最近更新 更多