从读取中的Json验证中获取NPE(在Scala中读取自定义类)



我有一个情况,我想读取一个对象,像这样:

Something(first: String, second: GPID)

GPID是Int的包装器(见下文)。

类可以很好地序列化(写),但是当我试图反序列化(读)然后验证时,Play抛出了一个NPE。

编辑

上面的例子已经简化了。我正在使用的实际代码有点复杂,所以我试图创建一个简单的示例。这是我正在处理的实际对象:

case class GPInviteRequest(token: String, userId: Option[GPID] = None, email: Option[String] = None, phoneNumber: Option[GPPhoneNumber] = None)
object GPInviteRequest {
    implicit val readsInvite = Json.reads[GPInviteRequest]
    implicit val writesInvite = Json.writes[GPInviteRequest]
}

GPID类型基本上是对Int的包装。所有被引用的对象(GPID, GPPhoneNumber)都有自己的读/写。在我的第一次尝试中,我得到:JsError(List((/userId/GPID,List(ValidationError(error.path.missing,WrappedArray())))))。这是因为我没有创建格式良好的JSON,但到目前为止还不错,服务器正确地报告了错误……那么,现在我已经编写了一个测试,它序列化然后反序列化对象:

    "serialize an invite request to/from JSON" in {
        val j1 = Json.toJson(GPInviteRequest("token@ab6f7ad89ce8ff", Option(GPID(1000))))
        println(j1.toString)
        j1.validate[GPInviteRequest].isSuccess must beTrue
    }

GPID是Int的包装器,但是,它的Reads方法看起来像这样:

object GPID {
    implicit val reads: Reads[GPID] = (
        (__  "GPID").read[GPID]
        )
...

好,那么,现在当我运行测试时,输出如下:

[info] The invite service should
[error]   ! serialize an invite request to/from JSON
{"token":"token@ab6f7ad89ce8ff","userId":{"GPID":1000}}
[error]    null (JsConstraints.scala:36)

为完整起见,下面是错误:

[error]    null (JsConstraints.scala:36)
[error] play.api.libs.json.PathReads$$anonfun$at$1$$anonfun$apply$2.apply(JsConstraints.scala:36)
[error] play.api.libs.json.PathReads$$anonfun$at$1$$anonfun$apply$2.apply(JsConstraints.scala:36)
[error] play.api.libs.json.JsResult$class.flatMap(JsResult.scala:103)
[error] play.api.libs.json.JsSuccess.flatMap(JsResult.scala:9)
[error] play.api.libs.json.PathReads$$anonfun$at$1.apply(JsConstraints.scala:36)
[error] play.api.libs.json.PathReads$$anonfun$at$1.apply(JsConstraints.scala:36)
[error] play.api.libs.json.Reads$$anon$8.reads(Reads.scala:101)
[error] play.api.libs.json.PathReads$$anonfun$nullable$1$$anonfun$apply$7$$anonfun$apply$9.apply(JsConstraints.scala:65)
[error] play.api.libs.json.PathReads$$anonfun$nullable$1$$anonfun$apply$7$$anonfun$apply$9.apply(JsConstraints.scala:63)
[error] play.api.libs.json.JsResult$class.fold(JsResult.scala:76)
[error] play.api.libs.json.JsSuccess.fold(JsResult.scala:9)
[error] play.api.libs.json.PathReads$$anonfun$nullable$1$$anonfun$apply$7.apply(JsConstraints.scala:61)
[error] play.api.libs.json.PathReads$$anonfun$nullable$1$$anonfun$apply$7.apply(JsConstraints.scala:61)
[error] play.api.libs.json.PathReads$$anonfun$nullable$1.apply(JsConstraints.scala:59)
[error] play.api.libs.json.PathReads$$anonfun$nullable$1.apply(JsConstraints.scala:58)
[error] play.api.libs.json.Reads$$anon$8.reads(Reads.scala:101)
[error] play.api.libs.json.Reads$$anon$3$$anon$4.reads(Reads.scala:81)
[error] play.api.libs.json.Reads$$anonfun$map$1.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anonfun$map$1.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anon$8.reads(Reads.scala:101)
[error] play.api.libs.json.Reads$$anon$3$$anon$4.reads(Reads.scala:81)
[error] play.api.libs.json.Reads$$anonfun$map$1.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anonfun$map$1.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anon$8.reads(Reads.scala:101)
[error] play.api.libs.json.Reads$$anon$3$$anon$4.reads(Reads.scala:81)
[error] play.api.libs.json.Reads$$anonfun$map$1.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anonfun$map$1.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anon$8.reads(Reads.scala:101)
[error] play.api.libs.json.JsValue$class.validate(JsValue.scala:73)
[error] play.api.libs.json.JsObject.validate(JsValue.scala:166)
[error] application.TestInviteServices$$anonfun$2$$anonfun$apply$3$$anonfun$apply$1.apply$mcZ$sp(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$2$$anonfun$apply$3$$anonfun$apply$1.apply(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$2$$anonfun$apply$3$$anonfun$apply$1.apply(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$2$$anonfun$apply$3.apply(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$2$$anonfun$apply$3.apply(TestInviteServices.scala:26)

问题在于您对Reads[GPID]的递归定义。GPID应该包裹Int,但Reads实际上并没有显示这一点。GPID.reads包含(__ "GPID).read[GPID], CC_7依赖于一个隐式的Reads[GPID]在作用域中。不幸的是,编译器允许GPID.reads自己满足这个需求,这会抛出一个NullPointerException,因为它试图在初始化之前访问自己。

case class GPID(GPID: Int)
object GPID {
    implicit val reads: Reads[GPID] = (__  "GPID").read[GPID]
    implicit val writes: Writes[GPID] = Json.writes[GPID]
}
scala> val gpid = GPID(1234)
gpid: GPID = GPID(1234)
scala> val js = Json.toJson(gpid)
js: play.api.libs.json.JsValue = {"GPID":1234}
scala> js.validate[GPID]
java.lang.NullPointerException
...

要解决这个问题,我们只需要将Reads[GPID]修改为不递归,这很容易。我们真的想要read[Int],因为这就是它所包装的。然后我们把map变成GPID

case class GPID(GPID: Int)
object GPID {
    implicit val reads: Reads[GPID] = (__  "GPID").read[Int].map(GPID(_))
    implicit val writes: Writes[GPID] = Json.writes[GPID]
}
scala> js.validate[GPID]
res2: play.api.libs.json.JsResult[GPID] = JsSuccess(GPID(1234),/GPID) // It works!

如果我们与你的其他代码合并,它现在可以工作了:

scala> val j1 = Json.toJson(GPInviteRequest("token@ab6f7ad89ce8ff", Option(GPID(1000))))
j1: play.api.libs.json.JsValue = {"token":"token@ab6f7ad89ce8ff","userId":{"GPID":1000}}
scala> j1.validate[GPInviteRequest]
res3: play.api.libs.json.JsResult[GPInviteRequest] = JsSuccess(GPInviteRequest(token@ab6f7ad89ce8ff,Some(GPID(1000)),None),)

你还没有显示它,但我有一种感觉,GPPhoneNumber将有同样的问题(假设它只是包装StringReads以类似的方式定义),但修复将是相同的。

具体是什么错误?你想要完成什么?

import play.api.libs.json.Json
case class Person(first: String, middle: Option[String], last: String)
object Person {
  implicit val reads = Json.reads[Person]
}
val json = """{ "first": "Michael", "middle": null, "last": "Kendra"}"""
val json2 = """{ "first": "Michael", "last": "Kendra"}"""

Json.parse(json).validate[Person]
# >> res0: play.api.libs.json.JsResult[Person] = JsSuccess(Person(Michael,None,Kendra),)
Json.parse(json2).validate[Person]
# >> res1: play.api.libs.json.JsResult[Person] = JsSuccess(Person(Michael,None,Kendra),)

最新更新