如何忽略 JSON 数组中的解码失败?
How do I ignore decoding failures in a JSON array?
假设我想将 JSON 数组中的一些值解码为 class 和 circe 的情况。以下工作正常:
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
形状的内容,这会导致解码错误:
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)))
我如何编写一个解码器来忽略数组中无法解码到我的案例中的任何内容class?
解决这个问题最直接的方法是使用解码器首先尝试将每个值解码为 Foo
,如果 Foo
解码器失败,则回退到身份解码器.大约 0.9 中的新 either
方法使这个通用版本实际上成为一个单行代码:
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@2b48626b
scala> decode(badDoc)(myTolerantFooDecoder)
res2: Either[io.circe.Error,List[Foo]] = Right(List(Foo(abc), Foo(xyz)))
分解步骤:
Decoder.decodeList
表示 "define a list decoder that tries to use the given decoder to decode each JSON array value"。
Decoder[A].either(Decoder[Json]
表示 "first try to decode the value as an A
, and if that fails decode it as a Json
value (which will always succeed), and return the result (if any) as a Either[A, Json]
"。
.map(_.flatMap(_.left.toOption))
表示 "take the resulting list of Either[A, Json]
values and remove all the Right
s"。
…它以一种相当简洁、组合的方式完成了我们想要的事情。在某些时候,我们可能希望将其捆绑到 circe 本身的实用方法中,但现在写出这个显式版本还不错。
假设我想将 JSON 数组中的一些值解码为 class 和 circe 的情况。以下工作正常:
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
形状的内容,这会导致解码错误:
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)))
我如何编写一个解码器来忽略数组中无法解码到我的案例中的任何内容class?
解决这个问题最直接的方法是使用解码器首先尝试将每个值解码为 Foo
,如果 Foo
解码器失败,则回退到身份解码器.大约 0.9 中的新 either
方法使这个通用版本实际上成为一个单行代码:
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@2b48626b
scala> decode(badDoc)(myTolerantFooDecoder)
res2: Either[io.circe.Error,List[Foo]] = Right(List(Foo(abc), Foo(xyz)))
分解步骤:
Decoder.decodeList
表示 "define a list decoder that tries to use the given decoder to decode each JSON array value"。Decoder[A].either(Decoder[Json]
表示 "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))
表示 "take the resulting list ofEither[A, Json]
values and remove all theRight
s"。
…它以一种相当简洁、组合的方式完成了我们想要的事情。在某些时候,我们可能希望将其捆绑到 circe 本身的实用方法中,但现在写出这个显式版本还不错。