Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/json/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Json";“验证”;玩_Json_Scala_Validation_Playframework_Playframework 2.0 - Fatal编程技术网

Json";“验证”;玩

Json";“验证”;玩,json,scala,validation,playframework,playframework-2.0,Json,Scala,Validation,Playframework,Playframework 2.0,对于request.body上的validate方法,它将json对象的属性名和值类型与模型定义中定义的属性名和值类型相匹配。现在,如果我向json对象添加一个额外的属性并尝试验证它,那么它将在不应该的情况下作为JsSuccess传递 { "Name": "Bob", "Age": 20, "Random_Field_Not_Defined_in_Models": "Test" } 我的Person类定义如下 case class Person(name: Strin

对于request.body上的validate方法,它将json对象的属性名和值类型与模型定义中定义的属性名和值类型相匹配。现在,如果我向json对象添加一个额外的属性并尝试验证它,那么它将在不应该的情况下作为JsSuccess传递

{ 
    "Name": "Bob",
    "Age": 20,
    "Random_Field_Not_Defined_in_Models": "Test"
}
我的Person类定义如下

case class Person(name: String, age: Int)

我假设您一直在使用内置的
Reads[T]
Format[T]
转换器,Play通过
Json为您提供这些转换器。Reads[T]
,例如:

import play.api.libs.json._

val standardReads = Json.reads[Person]
虽然这些方法非常方便,但如果您需要额外的验证,则必须定义一个自定义的
读取[Person]
类;但幸运的是,我们仍然可以利用内置的JSON to case类宏来执行基本检查和转换,然后在一切正常的情况下添加额外的自定义检查层:

val standardReads = Json.reads[Person]

val strictReads = new Reads[Person] {
  val expectedKeys = Set("name", "age")

  def reads(jsv:JsValue):JsResult[Person] = {
    standardReads.reads(jsv).flatMap { person =>
      checkUnwantedKeys(jsv, person)
    }
  }

  private def checkUnwantedKeys(jsv:JsValue, p:Person):JsResult[Person] = {
    val obj = jsv.asInstanceOf[JsObject]
    val keys = obj.keys
    val unwanted = keys.diff(expectedKeys)
    if (unwanted.isEmpty) {
      JsSuccess(p)
    } else {
      JsError(s"Keys: ${unwanted.mkString(",")} found in the incoming JSON")
    }
  } 
} 
请注意我们如何首先利用
标准阅读
,以确保我们处理的是可以转换为
人的东西。没有必要在这里重新发明轮子

如果我们从
standardReads
获得
JsError
,我们使用
flatMap
有效地缩短转换-即,我们仅在需要时调用
CheckUnventedKeys

checkUnventedKeys
只使用了
JsObject
的事实,因此我们可以轻松地对照白名单检查密钥的名称

请注意您也可以使用用于理解的工具编写
flatMap
,如果您需要更多的检查阶段,该工具看起来会更干净:

for {
    p <- standardReads.reads(jsv)
    r1 <- checkUnexpectedFields(jsv, p)
    r2 <- checkSomeOtherStuff(jsv, r1)
    r3 <- checkEvenMoreStuff(jsv, r2)
} yield r3
用于{

p如果您想避免太多的样板文件,可以使用一点scala反射生成更通用的解决方案:

import play.api.libs.json._
import scala.reflect.runtime.universe._

def checkedReads[T](underlyingReads: Reads[T])(implicit typeTag: TypeTag[T]): Reads[T] = new Reads[T] {

    def classFields[T: TypeTag]: Set[String] = typeOf[T].members.collect {
      case m: MethodSymbol if m.isCaseAccessor => m.name.decodedName.toString
    }.toSet

    def reads(json: JsValue): JsResult[T] = {
      val caseClassFields = classFields[T]
      json match {
        case JsObject(fields) if (fields.keySet -- caseClassFields).nonEmpty =>
          JsError(s"Unexpected fields provided: ${(fields.keySet -- caseClassFields).mkString(", ")}")
        case _ => underlyingReads.reads(json)
      }
    }

  }
然后,您可以将读取实例指定为:

implicit val reads = checkedReads(Json.reads[Person])
这充分利用了Scala类型的魔力和反射库(让您可以查看类上的字段)

classFields
方法不依赖于一组固定的字段,而是动态获取案例类的所有字段(键入param
T
)。它查看所有成员,只收集案例类访问器(否则我们会选择类似
toString
)的方法。它返回一个
set[String]
字段名的名称

您会注意到,
checkedReads
采用隐式的
TypeTag[T]
。这是编译器在编译时提供的,由
typeOf
方法使用


剩下的代码是不言自明的。如果传入的json与我们的第一个case匹配(它是一个
JsObject
,并且case类中没有字段)然后我们返回一个
JsError
。否则我们会将其传递给底层读取器。

正如您所注意到的,附加字段不会阻止
JsSuccess
结果。事实就是这样。如果case类中有json中不存在的可选字段,或者有默认值的字段,则此操作会失败。您能举个例子@Alex吗ITC?运行此示例,它尝试反序列化具有默认值字段的案例类,但失败的原因是
JsError(List(/value,List(JsonValidationError(List(error.path.missing),WrappedArray()))
,问题是代码中的这一部分
if(fields.keySet--caseClassFields).nonEmpty
,它希望所有的类字段都在JSON中,否则,它将返回一个包含缺少键的非空集合,请参见@AlexITC,为回复速度慢表示歉意。此答案显示了一种防止JSON与额外字段被解码的方法,它不会以您想要的方式更改
读取的内容。我已经修改了您的代码(请参阅)显示您正在访问底层读取实现的行为。Play JSON库可以通过提供支持它的底层
Reads
来读取默认值(如演示所示)。您可能需要查看。