从 Json 获取 NPE 在读取中验证(在 Scala 中读取自定义 class)
Getting NPE from Json validate in Reads (reading custom class in Scala)
我有一种情况,我想像这样读取一个对象:
Something(first: String, second: GPID)
GPID 是 Int 的包装器(见下文)。
class 可以很好地序列化(写入)但是当我尝试反序列化(读取)然后验证时,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)都有自己的 Reads/Writes。在我的第一次尝试中,我得到: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$$anonfun$apply.apply(JsConstraints.scala:36)
[error] play.api.libs.json.PathReads$$anonfun$at$$anonfun$apply.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.apply(JsConstraints.scala:36)
[error] play.api.libs.json.PathReads$$anonfun$at.apply(JsConstraints.scala:36)
[error] play.api.libs.json.Reads$$anon.reads(Reads.scala:101)
[error] play.api.libs.json.PathReads$$anonfun$nullable$$anonfun$apply$$anonfun$apply.apply(JsConstraints.scala:65)
[error] play.api.libs.json.PathReads$$anonfun$nullable$$anonfun$apply$$anonfun$apply.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$$anonfun$apply.apply(JsConstraints.scala:61)
[error] play.api.libs.json.PathReads$$anonfun$nullable$$anonfun$apply.apply(JsConstraints.scala:61)
[error] play.api.libs.json.PathReads$$anonfun$nullable.apply(JsConstraints.scala:59)
[error] play.api.libs.json.PathReads$$anonfun$nullable.apply(JsConstraints.scala:58)
[error] play.api.libs.json.Reads$$anon.reads(Reads.scala:101)
[error] play.api.libs.json.Reads$$anon$$anon.reads(Reads.scala:81)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anon.reads(Reads.scala:101)
[error] play.api.libs.json.Reads$$anon$$anon.reads(Reads.scala:81)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anon.reads(Reads.scala:101)
[error] play.api.libs.json.Reads$$anon$$anon.reads(Reads.scala:81)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anon.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$$anonfun$apply$$anonfun$apply.apply$mcZ$sp(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$$anonfun$apply$$anonfun$apply.apply(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$$anonfun$apply$$anonfun$apply.apply(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$$anonfun$apply.apply(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$$anonfun$apply.apply(TestInviteServices.scala:26)
您具体遇到了什么错误?你想达到什么目的?这对我有用:
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),)
问题是你对 Reads[GPID]
的递归定义。 GPID
应该包裹 Int
,但 Reads
实际上并没有显示出来。 GPID.reads
包含 (__ \ "GPID).read[GPID]
,它依赖于范围内的隐式 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
即Int
变成了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
会有同样的问题(假设它只是包装了一个 String
而 Reads
定义在一个类似的方式),但修复将是相同的。
我有一种情况,我想像这样读取一个对象:
Something(first: String, second: GPID)
GPID 是 Int 的包装器(见下文)。
class 可以很好地序列化(写入)但是当我尝试反序列化(读取)然后验证时,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)都有自己的 Reads/Writes。在我的第一次尝试中,我得到: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$$anonfun$apply.apply(JsConstraints.scala:36)
[error] play.api.libs.json.PathReads$$anonfun$at$$anonfun$apply.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.apply(JsConstraints.scala:36)
[error] play.api.libs.json.PathReads$$anonfun$at.apply(JsConstraints.scala:36)
[error] play.api.libs.json.Reads$$anon.reads(Reads.scala:101)
[error] play.api.libs.json.PathReads$$anonfun$nullable$$anonfun$apply$$anonfun$apply.apply(JsConstraints.scala:65)
[error] play.api.libs.json.PathReads$$anonfun$nullable$$anonfun$apply$$anonfun$apply.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$$anonfun$apply.apply(JsConstraints.scala:61)
[error] play.api.libs.json.PathReads$$anonfun$nullable$$anonfun$apply.apply(JsConstraints.scala:61)
[error] play.api.libs.json.PathReads$$anonfun$nullable.apply(JsConstraints.scala:59)
[error] play.api.libs.json.PathReads$$anonfun$nullable.apply(JsConstraints.scala:58)
[error] play.api.libs.json.Reads$$anon.reads(Reads.scala:101)
[error] play.api.libs.json.Reads$$anon$$anon.reads(Reads.scala:81)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anon.reads(Reads.scala:101)
[error] play.api.libs.json.Reads$$anon$$anon.reads(Reads.scala:81)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anon.reads(Reads.scala:101)
[error] play.api.libs.json.Reads$$anon$$anon.reads(Reads.scala:81)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anonfun$map.apply(Reads.scala:28)
[error] play.api.libs.json.Reads$$anon.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$$anonfun$apply$$anonfun$apply.apply$mcZ$sp(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$$anonfun$apply$$anonfun$apply.apply(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$$anonfun$apply$$anonfun$apply.apply(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$$anonfun$apply.apply(TestInviteServices.scala:31)
[error] application.TestInviteServices$$anonfun$$anonfun$apply.apply(TestInviteServices.scala:26)
您具体遇到了什么错误?你想达到什么目的?这对我有用:
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),)
问题是你对 Reads[GPID]
的递归定义。 GPID
应该包裹 Int
,但 Reads
实际上并没有显示出来。 GPID.reads
包含 (__ \ "GPID).read[GPID]
,它依赖于范围内的隐式 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
即Int
变成了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
会有同样的问题(假设它只是包装了一个 String
而 Reads
定义在一个类似的方式),但修复将是相同的。