假设我有一个类似于以下的case类,并且我想将一个JSON对象解码为其中,所有未使用的字段都以一个剩余的剩余对象结尾:
import io.circe.Json
case class Foo(a: Int, b: String, leftovers: Json)
在带圆圈的Scala中执行此操作的最佳方法是什么?
(注意:我见过类似a few times这样的问题,因此我为后代进行问答。)
答案 0 :(得分:10)
有两种方法可以解决此问题。一种相当简单的方法是过滤出解码后使用的密钥:
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
}))
后一种方法不需要您在多个位置更改使用的字段,它还具有看起来更像您手动编写的任何其他解码器的优点。