Scala 玩 json:如何捕获整个 JSON 源?

问题描述 投票:0回答:3

我在我的客户端收到一个 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。

我不知道我怎么能完成它。如果我将路径指定为

(__)
,这是一条错误的路径,解析器将失败。如果我将路径指定为
(__ \ "")
,解析器会寻找一个名为
""
的字段,这显然是缺失的。

除了手动解析(用我自己的双手)之外,还有什么合理的解决方案吗?

json scala playframework play-json
3个回答
1
投票

一种方法:

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])
}

如果数据发生变化而无需更新解析器,则第二个优势是自动调整。


0
投票

如果

fields of your case class
same fields and types of json fields
,你不需要做手动映射。您可以直接使用隐式 val 使用
Json.writes[A]
进行序列化,
Json.reads[A]
进行反序列化或
Json.format[A]
进行序列化和反序列化。

这里在官方文档中展示了如何做json自动映射


0
投票

你非常接近,但是使用

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),
    )
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.