Как игнорировать ошибки декодирования в массиве JSON? - PullRequest
0 голосов
/ 04 июня 2018

Предположим, я хочу декодировать некоторые значения из массива JSON в класс дел с 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)))

Как я могу написать декодер, который игнорирует что-либо в массиве, которое не может быть декодировано в мой класс case?

1 Ответ

0 голосов
/ 04 июня 2018

Самый простой способ решения этой проблемы - использовать декодер, который сначала пытается декодировать каждое значение как Foo, а затем возвращается к декодеру идентификации в случае сбоя декодера Foo.Новый метод either в circe 0.9 делает универсальную версию этого практически однострочной:

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».

… что делает то, что мы хотим, довольно кратким, композиционным способом.В какой-то момент мы могли бы захотеть объединить это в служебный метод в самом circe, но сейчас написание этой явной версии не так уж и плохо.

...