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

11

假设我有一个像下面这样的 case class,并且我想将一个 JSON 对象解码为它,其中所有未使用的字段最终都被放在一个特殊的成员变量中:

import io.circe.Json

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

使用circe在Scala中最好的方法是什么?

(注意:我已经多次看到类似的问题,因此我为了记录而进行问答。)

1个回答

15

有几种方法可以解决这个问题。其中一种相当简单的方法是,在解码后过滤掉已使用的密钥:

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的状态单子编码工具:

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解码器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接