Десериализация списка Circe с отчетом о лучших попытках и ошибках - PullRequest
0 голосов
/ 12 сентября 2018

Я использую Circe для десериализации json, содержащего список. Иногда несколько элементов в списке json повреждены, что приводит к сбою всей десериализации. Вместо этого я хочу, чтобы Цирце сделал лучшую попытку и вернул список всех успешно десериализованных элементов списка вместе со списком ошибок для поврежденных элементов. Как это лучше всего сделать в Цирцее?

В частности, допустим, я пытаюсь десериализовать это:

val json = """{ "params": {
  "playlist": {
      "name": "Sample Playlist",
      "items": [
        {
          "clipId":"xyz", 
          "name":"abc",
          "properties": {
            "cat": "siamese",
            "dog": "spaniel"            
          }
        },
        {
          "clipId":"pqr", 
          "name":"def",
          "properties": {
            "cat": "tabby",
            "dog": "terrier"
          }
        } 
      ]
   }
}}"""

Я делаю это с:

import io.circe.Decoder, io.circe.generic.auto._
import scala.util._   

case class Clip(clipId: String, name: String, dog: String)
implicit val decodeClip: Decoder[Clip] = Decoder.instance { c =>
    for {
      id <- c.get[String]("clipId")
      name <- c.get[String]("name")
      dog <- c.downField("properties").get[String]("dog")
    } yield {
      Clip(id, name, dog)
    }
}

val decodeClipsParam = Decoder[List[Clip]].prepare(
  _.downField("params").downField("playlist").downField("items")
)

def deserializedThing(theJson: String) = io.circe.parser.decode(theJson)(decodeClipsParam)

Отлично работает и десериализуется правильно:

scala> deserializedThing(json)
res1: Either[io.circe.Error,List[circeLab.circeLab.Clip]] = Right(List(Clip(xyz,abc,spaniel), Clip(pqr,def,terrier)))

Но если я теперь испорчу один элемент списка json (изменив один из ключей "dog" на "doggg" скажем), то вся десериализация завершится неудачей - это не даст мне список не поврежденных Clip элементы, это просто говорит мне, что это не удалось.

Так что вместо десериализации в List[Clip] я бы хотел десериализовать в List[Try[Clip]], где каждый элемент похож на Success(Clip(xyz,abc,spaniel)) или Failure(ErrorDescriptionForThatItem).

Мне удалось добиться этого в Argonaut (используя довольно некрасивый код), но я не могу понять синтаксис в Circe. Какой лучший способ достичь этого? Спасибо!

1 Ответ

0 голосов
/ 22 сентября 2018

Хорошо, значит, это решение работает:

import io.circe.{Json, Decoder}
import io.circe.parser.parse 
import scala.util.{Try, Success, Failure} 

// Throw this exception if the list of items can't even be retrieved
case class ParseException(msg: String) extends Exception(msg) 

case class Clip(clipId: String, name: String, dog: String)
// This is the decoder that tries to decode an individual item
implicit val decodeClip: Decoder[Clip] = Decoder.instance { c =>
    for {
      id <- c.get[String]("clipId")
      name <- c.get[String]("name")
      dog <- c.downField("properties").get[String]("dog")
    } yield {
      Clip(id, name, dog)
    }
}


// Turn a string into a json doc
def jsonDoc(str: String) = parse(str).getOrElse(Json.Null)

// Attempt to retrieve the list of json objects appearing in "items"
def getListOfItemsAsJsonObjects(doc: Json): Try[List[Json]] = doc.hcursor.downField("params").downField("playlist").downField("items").focus match {
  case None => Failure(ParseException("Couldn't get items"))
  case Some(obj) => obj.asArray match {
    case None => Failure(ParseException("Couldn't turn to array"))
    case Some(arr) => Success(arr.toList)
  }
}


// Finally, map each json object from Items to a Try[Clip], so individual corrupted items don't affect others.
def tryListOfTries(str: String) = getListOfItemsAsJsonObjects(jsonDoc(str)).map(ok => ok.map(_.as[Clip].toTry))

Если в строке json нет искажений, tryListOfTries(json) вернет это:

Success(List(Success(Clip(xyz,abc,spaniel)), Success(Clip(pqr,def,terrier))))

Если вы испортили отдельное лицо item, вы получите что-то вроде этого, с другим items, декодированным нормально:

Success(List(Failure(DecodingFailure(Attempt to decode value on failed cursor, List(DownField(dog), DownField(properties)))), Success(Clip(pqr,def,terrier))))

И если вы испортили что-либо на более высоком уровне, чтобы оно не могло даже получить массив items, тогда вы получите Failure на верхнем уровне:

Failure(ParseException: Couldn't get items)

Не уверен, есть ли более идиоматическое решение, но я не смог его найти, поэтому надеюсь, что это кому-нибудь поможет.

...