基于另一个验证的自定义验证
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 对象时进行这些验证。有什么建议吗?
您可以使用 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。
我们希望将我们的某些 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 对象时进行这些验证。有什么建议吗?
您可以使用 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。