基于另一个验证的自定义验证

Custom validations based on another validation

我们希望将我们的某些 JSON 验证基于先前验证的验证结果。

case class InsideObject(name: Option[String], companyName: Option[String], eType: Int)

我们有上面的案例 class,我们想引入一个 JSON 验证,如果 eType = 1 或使 companyName 如果 eType = 2 加上我们在读取 JSON 对象时已经存在的每个字段的一些更具体的验证,则需要。

implicit val insideObjectReads: Reads[InsideObject] = (
        (JsPath \ "name").readNullable[String]
          .filter(JsonValidationError("must be 10 digits"))(name => name.getOrElse("").length == 10) and
        (JsPath \ "companyName").readNullable[String]
          .filter(JsonValidationError("must not be optional"))(companyName => companyName.isDefined) and
    (JsPath \ "eType").read[Int]
          .filter(JsonValidationError("eType can only take values 1 or 2"))(eType => eType == 1 || eType == 2)
    )(InsideObject.apply _)

我们可以在 apply 方法中进行这些验证,但我们希望在读取 JSON 对象时进行这些验证。有什么建议吗?

示例代码:https://scastie.scala-lang.org/wTuhI1zCSJSWKu9Lltcqbw

您可以使用 orElse 链接读取。

val reads1: Reads[InsideObject] = (
  (JsPath \ "name").readNullable[String]
    .filter(JsonValidationError("must not be optional"))(name => name.isDefined) and
    (JsPath \ "companyName").readNullable[String] and
    (JsPath \ "eType").read[Int]
      .filter(JsonValidationError("eType can only take values 1 or 2"))(eType => eType == 1)
  ) (InsideObject.apply _)

implicit val reads2: Reads[InsideObject] = (
  (JsPath \ "name").readNullable[String] and
    (JsPath \ "companyName").readNullable[String]
      .filter(JsonValidationError("must not be optional"))(companyName => companyName.isDefined) and
    (JsPath \ "eType").read[Int]
      .filter(JsonValidationError("eType can only take values 1 or 2"))(eType => eType == 2)
  ) (InsideObject.apply _).orElse(reads1)

此处,在第一个 Reads 中,需要 companyName 且 eType 为 2。如果结果为 JsError,则第二个运行需要名称且 eType 为 1

--编辑--

假设您的案例 class 有如下额外字段:

case class InsideObject(age: Option[Int], location: Option[String], name: Option[String], companyName: Option[String], eType: Int)

object InsideObject {
  implicit val writesPublicLeadFormRequest: OFormat[InsideObject] = Json.format[InsideObject]

  def apply(age: Option[Int], location: Option[String],name: Option[String], companyName: Option[String], eType: Int) =
  new InsideObject(age, location, name, companyName, eType)
}

您可以结合 Reads

val reads0 = (JsPath \ "age").readNullable[Int] and
  (JsPath \ "location").readNullable[String]

val reads1: Reads[InsideObject] = (
  reads0 and
  (JsPath \ "name").readNullable[String]
    .filter(JsonValidationError("must not be optional"))(name => name.isDefined) and
    (JsPath \ "companyName").readNullable[String] and
    (JsPath \ "eType").read[Int]
      .filter(JsonValidationError("eType can only take values 1 or 2"))(eType => eType == 1)
  ) (InsideObject.apply _)

implicit val reads2: Reads[InsideObject] = (
  reads0 and
  (JsPath \ "name").readNullable[String] and
    (JsPath \ "companyName").readNullable[String]
      .filter(JsonValidationError("must not be optional"))(companyName => companyName.isDefined) and
    (JsPath \ "eType").read[Int]
      .filter(JsonValidationError("eType can only take values 1 or 2"))(eType => eType == 2)
  ) (InsideObject.apply _).orElse(reads1)

希望这会解决评论问题。

--编辑-2--

您还可以在 json 上进行模式匹配,我想这会在这种特殊情况下提供更好的灵活性。示例代码:

implicit val reads3: Reads[InsideObject] = {
  case JsObject(map @ Seq(("age", JsNumber(age)), ("location", JsString(location)), ("name", JsString(name)), ("companyName", JsString(companyName)), ("eType", JsNumber(eType)))) =>
    if (eType == 1 && name.nonEmpty)
      JsSuccess(InsideObject(Some(age.intValue), Some(location), Some(name), Some(companyName), 1))
    else if (eType == 2 && companyName.nonEmpty)
      JsSuccess(InsideObject(Some(age.intValue), Some(location), Some(name), Some(companyName), 2))
    else
      JsError("Custom Error - wrong eType?")
  case _ => JsError("Custom Error - wrong number of fields?")
}

除了 Seq 上的模式匹配,您还可以使用基础 Map 进行进一步验证。仅使用 Seq 时,请仔细检查字段数,即缺少字段将导致上述代码段中的 JsError

--编辑-3--

进一步检查后,我注意到 Edit-2 没有按预期工作。我正在使用较旧的 play-json 和 scala 版本进行测试。在 scastie 片段中,我注意到您使用的是 2.10.0-RC5。下面的解决方案是用 2.10.0-RC5.

测试的
implicit val reads4: Reads[InsideObject] = {
  case JsObject(map) =>
    val opt: Option[(Option[String], Option[String], Int)] = for {
      eType <- map.get("eType").map(_.as[Int])
      name = map.get("name").flatMap(_.asOpt[String])
      company = map.get("companyName").flatMap(_.asOpt[String])
    } yield (name, company, eType)
    opt match {
      case Some((name, company, eType)) =>
        if (eType == 1 && name.isDefined)
          JsSuccess(InsideObject(name, company, 1))
        else if (eType == 2 && company.isDefined)
          JsSuccess(InsideObject(name, company, 2))
        else
          JsError("Custom Error - wrong eType?")
      case None => JsError("Custom Error - wrong number of fields")
    }

  case _ => JsError("Custom Error - wrong type?")
}

可以看到更新的版本here