我假设您一直在使用 Play 通过 Json.reads[T] 为您提供的内置 Reads[T] 或 Format[T] 转换器,例如:
import play.api.libs.json._
val standardReads = Json.reads[Person]
虽然这些超级很方便,但如果您需要附加验证,您必须定义一个自定义Reads[Person] 类;但幸运的是,我们仍然可以利用内置的 JSON-to-case-class 宏来进行基本的检查和转换,然后在一切正常的情况下添加额外的自定义检查层:
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 first,以确保我们正在处理可以转换为@987654329的东西@。无需在这里重新发明轮子。
如果我们从standardReads 获得JsError,我们使用flatMap 来有效地缩短转换 - 即我们只在需要时调用checkUnwantedKeys。
checkUnwantedKeys 只是利用JsObject 是really just a wrapper around a Map 的事实,因此我们可以轻松地对照白名单检查密钥的名称。
请注意,您也可以使用 for-comprehension 编写 flatMap,如果您需要更多检查阶段,它开始看起来更清晰:
for {
p <- standardReads.reads(jsv)
r1 <- checkUnexpectedFields(jsv, p)
r2 <- checkSomeOtherStuff(jsv, r1)
r3 <- checkEvenMoreStuff(jsv, r2)
} yield r3