假设我需要解码如下所示的 JSON 数组,其中开头有几个字段,一些任意数量的同质元素,然后是其他一些字段:
[ "Foo", "McBar", true, false, false, false, true, 137 ]
我不知道为什么有人会选择这样对他们的数据进行编码,但是人们会做一些奇怪的事情,假设在这种情况下我只需要处理它。
我想将此 JSON 解码为这样的案例类:
case class Foo(firstName: String, lastName: String, age: Int, stuff: List[Boolean])
我们可以这样写:
import cats.syntax.either._
import io.circe.{ Decoder, DecodingFailure, Json }
implicit val fooDecoder: Decoder[Foo] = Decoder.instance { c =>
c.focus.flatMap(_.asArray) match {
case Some(fnJ +: lnJ +: rest) =>
rest.reverse match {
case ageJ +: stuffJ =>
for {
fn <- fnJ.as[String]
ln <- lnJ.as[String]
age <- ageJ.as[Int]
stuff <- Json.fromValues(stuffJ.reverse).as[List[Boolean]]
} yield Foo(fn, ln, age, stuff)
case _ => Left(DecodingFailure("Foo", c.history))
}
case None => Left(DecodingFailure("Foo", c.history))
}
}
…有效:
scala> fooDecoder.decodeJson(json"""[ "Foo", "McBar", true, false, 137 ]""")
res3: io.circe.Decoder.Result[Foo] = Right(Foo(Foo,McBar,137,List(true, false)))
但是,这太可怕了。错误消息也完全没用:
scala> fooDecoder.decodeJson(json"""[ "Foo", "McBar", true, false ]""")
res4: io.circe.Decoder.Result[Foo] = Left(DecodingFailure(Int, List()))
当然有一种方法可以做到这一点,它不涉及在光标和值之间来回切换Json
,在我们的错误消息中丢弃历史,并且通常只是令人眼花缭乱?
一些上下文:关于编写像这样的自定义 JSON 数组解码器的问题经常出现(例如今天早上)。如何做到这一点的具体细节可能会在即将发布的 circe 版本中发生变化(尽管 API 会类似;有关一些细节,请参阅这个实验项目),所以我真的不想花很多时间添加一个像这样的文档示例,但它出现的足够多,我认为它确实值得堆栈溢出问答。