问题描述
假设我想使用 circe 将JSON数组中的某些值解码为case类.以下工作正常:
Suppose I want to decode some values from a JSON array into a case class with circe. The following works just fine:
scala> import io.circe.generic.auto._, io.circe.jawn.decode
import io.circe.generic.auto._
import io.circe.jawn.decode
scala> case class Foo(name: String)
defined class Foo
scala> val goodDoc = """[{ "name": "abc" }, { "name": "xyz" }]"""
goodDoc: String = [{ "name": "abc" }, { "name": "xyz" }]
scala> decode[List[Foo]](goodDoc)
res0: Either[io.circe.Error,List[Foo]] = Right(List(Foo(abc), Foo(xyz)))
有时候,我正在解码的JSON数组包含其他非Foo
形的东西,这会导致解码错误:
It's sometimes the case that the JSON array I'm decoding contains other, non-Foo
-shaped stuff, though, which results in a decoding error:
scala> val badDoc =
| """[{ "name": "abc" }, { "id": 1 }, true, "garbage", { "name": "xyz" }]"""
badDoc: String = [{ "name": "abc" }, { "id": 1 }, true, "garbage", { "name": "xyz" }]
scala> decode[List[Foo]](badDoc)
res1: Either[io.circe.Error,List[Foo]] = Left(DecodingFailure(Attempt to decode value on failed cursor, List(DownField(name), MoveRight, DownArray)))
我该如何编写一个解码器来忽略数组中无法解码为我的case类的所有内容?
How can I write a decoder that ignores anything in the array that can't be decoded into my case class?
推荐答案
解决此问题的最直接方法是使用解码器,该解码器首先尝试将每个值解码为Foo
,然后返回到恒等式Foo
解码器失败时解码器.大约在0.9版中的新either
方法使该通用版本实际上变成了单行代码:
The most straightforward way to solve this problem is to use a decoder that first tries to decode each value as a Foo
, and then falls back to the identity decoder if the Foo
decoder fails. The new either
method in circe 0.9 makes the generic version of this practically a one-liner:
import io.circe.{ Decoder, Json }
def decodeListTolerantly[A: Decoder]: Decoder[List[A]] =
Decoder.decodeList(Decoder[A].either(Decoder[Json])).map(
_.flatMap(_.left.toOption)
)
它是这样的:
scala> val myTolerantFooDecoder = decodeListTolerantly[Foo]
myTolerantFooDecoder: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon$21@2b48626b
scala> decode(badDoc)(myTolerantFooDecoder)
res2: Either[io.circe.Error,List[Foo]] = Right(List(Foo(abc), Foo(xyz)))
要分解步骤:
-
Decoder.decodeList
说:定义一个列表解码器,尝试使用给定的解码器解码每个JSON数组值". -
Decoder[A].either(Decoder[Json]
说:首先尝试将值解码为A
,如果失败,则将其解码为Json
值(将始终成功),然后将结果(如果有)返回为Either[A, Json]
". -
.map(_.flatMap(_.left.toOption))
说获取得到的Either[A, Json]
值列表并删除所有Right
s".
Decoder.decodeList
says "define a list decoder that tries to use the given decoder to decode each JSON array value".Decoder[A].either(Decoder[Json]
says "first try to decode the value as anA
, and if that fails decode it as aJson
value (which will always succeed), and return the result (if any) as aEither[A, Json]
"..map(_.flatMap(_.left.toOption))
says "take the resulting list ofEither[A, Json]
values and remove all theRight
s".
…这以一种简洁,结构化的方式完成了我们想要的工作.在某些时候,我们可能希望将其捆绑成一个实用工具,但现在写出这个显式版本还不错.
…which does what we want in a fairly concise, compositional way. At some point we might want to bundle this up into a utility method in circe itself, but for now writing out this explicit version isn't too bad.
这篇关于如何忽略JSON数组中的解码失败?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!