我在我的客户端收到一个 WSResponse,并使用 play 的
deserializeJson
方法提取数据,由路径指定,例如
implicit val lsmf: Format[MyData] = (
(__).formatNullable[JsValue] ~
(__ \ "id").format[Int] ~
(__ \ "name").format[String])
(MyData.apply, unlift(MyData.unapply))
接收类看起来像
case class MyData(
json: JsValue,
id: Int,
name: String) {...}
看,解析数据的第一个成员应该包含接收到的整个 JSON。
我不知道我怎么能完成它。如果我将路径指定为
(__)
,这是一条错误的路径,解析器将失败。如果我将路径指定为(__ \ "")
,解析器会寻找一个名为""
的字段,这显然是缺失的。
除了手动解析(用我自己的双手)之外,还有什么合理的解决方案吗?
一种方法:
implicit val reads: Reads[MyData] = (json: JsValue) => {
val id = (json \ "id").as[Int] // or another way to extract it
// Same for other fields...
MyData(json, id, name)
}
// Do you even need a Writes? Not sure it makes much sense.
implicit val writes: Writes[MyData] = (data: MyData) => {
data.json
// Or:
JsObject("id" -> JsNumber(data.id), ...)
}
另一种方法是通过使用包装器类来依赖 Play 的宏:
case class ActualData(id: Long, name: String)
case class MyData(json: JsValue, data: ActualData)
implicit val actualFormat: Format[ActualData] = Json.format[ActualData]
implicit val myReads: Reads[MyData] = (json: JsValue) => {
MyData(json, json.as[ActualData])
}
如果数据发生变化而无需更新解析器,则第二个优势是自动调整。
如果
fields of your case class
有same fields and types of json fields
,你不需要做手动映射。您可以直接使用隐式 val 使用 Json.writes[A]
进行序列化,Json.reads[A]
进行反序列化或 Json.format[A]
进行序列化和反序列化。
这里在官方文档中展示了如何做json自动映射
你非常接近,但是使用
formatNullable
而不是 format
没有意义,因为你正在寻找 JsValue
而不是 Option[JsValue]
。 JsObject
也会工作。
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class MyData(
json: JsObject,
id: Int,
name: String
)
implicit val fmt: Format[MyData] = (
(__).format[JsObject] ~
(__ \ "id").format[Int] ~
(__ \ "name").format[String]
)(MyData.apply _, unlift(MyData.unapply))
val js = Json.parse("""
{
"id": 123,
"name": "test",
"foo": {
"bar": 123
},
"baz": false
}
""")
产量:
scala> js.as[MyData]
val res0: MyData = MyData({"id":123,"name":"test","foo":{"bar":123},"baz":false},123,test)
scala> res0.copy(id = 456)
val res1: MyData = MyData({"id":123,"name":"test","foo":{"bar":123},"baz":false},456,test)
scala> Json.toJson(res1)
val res2: play.api.libs.json.JsValue = {"foo":{"bar":123},"name":"test","baz":false,"id":456}
注意,使用
res1
,你甚至可以修改字段,它们仍然会被正确序列化(假设原始json值在前)。
更手动的方法是这样的:
implicit val fmt: Format[MyData] = new Format[MyData] {
def reads(js: JsValue): JsResult[MyData] = for {
json <- js.validate[JsObject]
id <- (js \ "id").validate[Int]
name <- (js \ "name").validate[String]
} yield MyData(json, id, name)
def writes(value: MyData): JsValue = {
value.json ++ Json.obj(
"id" -> Json.toJson(value.id),
"name" -> Json.toJson(value.name),
)
}
}