我有一个情况,我想读取一个对象,像这样:
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
将有同样的问题(假设它只是包装String
和Reads
以类似的方式定义),但修复将是相同的。
具体是什么错误?你想要完成什么?
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),)