在 scala 中播放 json:使用未知字段反序列化 json 而不会丢失它们

play json in scala: deserializing json with unknown fields without losing them

考虑我有一个 json 如下:

 {
  "a": "aa",
  "b": "bb",
  "c": "cc",
  "d": "dd", // unknown in advance
  "e": { //unknown in advance
    "aa": "aa"
  }
}

我确定 json 将包含 a、b、c,但我不知道这个 json 可能包含哪些其他字段。

我想将此 JSON 序列化为包含 a、b、c 的案例 class,但另一方面又不想丢失其他字段(将它们保存在地图中,因此 class 将被反序列化为与接收到的相同的 json。

想法?

您可以为此使用自定义 Reads,例如:

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class MyData(a: String, b: String, c:String, other: Map[String, JsValue])

object MyData {
  val abcReader: Reads[(String, String, String)] = (
    (JsPath \ "a").read[String] and
    (JsPath \ "b").read[String] and
    (JsPath \ "c").read[String]
  ).tupled

  implicit val reader: Reads[MyData] = Reads { json =>
    abcReader.reads(json).map {
      case (a, b, c) =>
        val other = json.as[JsObject].value -- Seq("a", "b", "c")
        MyData(a, b, c, other.toMap)
    }
  }
}

一个选项是捕获 Map[String,JsValue] 中的 "unknown" 字段,您可以在以后需要时从中提取值。

case class MyClass(a: String, b: String, c: String, extra: Map[String, JsValue])
implicit val reads: Reads[MyClass] = (
  (__ \ "a").read[String] and
  (__ \ "b").read[String] and
  (__ \ "c").read[String] and
  __.read[Map[String, JsValue]]
    .map(_.filterKeys(k => !Seq("a", "b", "c").contains(k)))
)(MyClass.apply _)

// Result:
// MyClass(aa,bb,cc,Map(e -> {"aa":"aa"}, d -> "dd"))

同样,您可以像这样执行 WritesFormat

// And a writes...
implicit val writes: Writes[MyClass] = (
  (__ \ "a").write[String] and
  (__ \ "b").write[String] and
  (__ \ "c").write[String] and
  __.write[Map[String, JsValue]]
)(unlift(MyClass.unapply _))

// Or combine the two...
implicit val format: Format[MyClass] = (
  (__ \ "a").format[String] and
  (__ \ "b").format[String] and
  (__ \ "c").format[String] and
  __.format[Map[String, JsValue]](Reads
    .map[JsValue].map(_.filterKeys(k => !Seq("a", "b", "c").contains(k))))
)(MyClass.apply, unlift(MyClass.unapply))

注意:它看起来有点混乱,因为你给 format for Map[String,JsValue] 一个明确的 Reads 作为参数 (Reads.map),然后你转换 (使用 .map 方法 ) 删除已经捕获的值。