使用 circe 解码 JSON 对象时捕获未使用的字段

Posted

技术标签:

【中文标题】使用 circe 解码 JSON 对象时捕获未使用的字段【英文标题】:Capturing unused fields while decoding a JSON object with circe 【发布时间】:2018-12-23 21:17:22 【问题描述】:

假设我有一个像下面这样的案例类,我想将一个 JSON 对象解码到其中,所有未使用的字段都以一个特殊的成员结尾:

import io.circe.Json

case class Foo(a: Int, b: String, leftovers: Json)

在 Scala 中使用 circe 执行此操作的最佳方法是什么?

(注意:我已经看到类似a few times 的问题,所以我正在为后代答疑解惑。)

【问题讨论】:

【参考方案1】:

有几种方法可以解决这个问题。一种相当直接的方法是过滤掉解码后使用的密钥:

import io.circe. Decoder, Json, JsonObject 

implicit val decodeFoo: Decoder[Foo] =
  Decoder.forProduct2[Int, String, (Int, String)]("a", "b")((_, _)).product(
    Decoder[JsonObject]
  ).map 
    case ((a, b), all) =>
      Foo(a, b, Json.fromJsonObject(all.remove("a").remove("b")))
  

如您所愿:

scala> val doc = """ "something": false, "a": 1, "b": "abc", "0": 0 """
doc: String =  "something": false, "a": 1, "b": "abc", "0": 0 

scala> io.circe.jawn.decode[Foo](doc)
res0: Either[io.circe.Error,Foo] =
Right(Foo(1,abc,
  "something" : false,
  "0" : 0
))

这种方法的缺点是您必须维护代码以将您使用过的密钥与其使用分开,这很容易出错。另一种方法是使用 circe 的 state-monad-powered 解码工具:

import cats.data.StateT
import cats.instances.either._
import io.circe. ACursor, Decoder, Json 

implicit val decodeFoo: Decoder[Foo] = Decoder.fromState(
  for 
    a <- Decoder.state.decodeField[Int]("a")
    b <- Decoder.state.decodeField[String]("b")
    rest <- StateT.inspectF((_: ACursor).as[Json])
   yield Foo(a, b, rest)
)

它的工作方式与之前的解码器相同(除了在解码失败时会出现一些小的错误差异):

scala> io.circe.jawn.decode[Foo](doc)
res1: Either[io.circe.Error,Foo] =
Right(Foo(1,abc,
  "something" : false,
  "0" : 0
))

后一种方法不需要您在多个位置更改已使用的字段,而且它还具有看起来更像您在circe 中手动编写的任何其他解码器的优点。

【讨论】:

以上是关于使用 circe 解码 JSON 对象时捕获未使用的字段的主要内容,如果未能解决你的问题,请参考以下文章

如何在circe中编码/解码json的时间戳?

使用 Circe Json 为啥隐式解析在运行时会变慢

无法使用Circe JSON解析器创建遍历JSON字符串的对象

使用 circe 在 Scala 中 JSON 将嵌套字段解码为 Map[String, String]

如何在不消除对象歧义的情况下使用 circe 解码 ADT

用于编码/解码 arity 0 的密封特征实例的 Circe 实例?