Cannot decode Sets in Circe

308 views Asked by At

I am trying to decode this piece of Json:

{
  "id" : "e07cff6a-bbf7-4bc9-b2ec-ff2ea8e46288",
  "paper" : {
    "title" : "Example Title",
    "authors" : [
      "1bf5e911-8878-4e06-ba8e-8159aadb052c"
    ]
  }
}

However, when it get to the Sets part, its fails. The error message is not helpful.

DecodingFailure([A]Set[A], List())

Here are my docoders:

  implicit val paperIdDecoder: Decoder[PaperId] = Decoder.decodeString.emap[PaperId] { str ⇒
    Either.catchNonFatal(PaperId(str)).leftMap(_.getMessage)
  }

  implicit val paperAuthorDecoder: Decoder[PaperAuthor] = Decoder.decodeString.emap[PaperAuthor] { str ⇒
    Either.catchNonFatal(PaperAuthor(str)).leftMap(_.getMessage)
  }

  implicit val paperDecoder: Decoder[Paper] = {
    for {
      title <- Decoder.decodeString
      authors <- Decoder.decodeSet[PaperAuthor]
    } yield Paper(title, authors)
  }

  implicit val paperViewDecoder: Decoder[PublishedPaperView] = for {
    id <- Decoder[PaperId]
    paper <- Decoder[Paper]
  } yield PublishedPaperView(id, paper) 

Here are the case classes used:

case class PublishedPaperView(id: PaperId, paper: Paper)

case class PaperId(value: String) 

case class Paper(title: String, authors: Set[PaperAuthor])

case class PaperAuthor(value: String)
1

There are 1 answers

0
Edmondo On BEST ANSWER

Although the error description is far from being explicative, your problem is related to a wrong usage of the monadic API of the decoder: remember that a for comprehension is a syntactic sugar for map/flatMap.

From io.circe.Decoder

  /**
   * Monadically bind a function over this [[Decoder]].
   */
  final def flatMap[B](f: A => Decoder[B]): Decoder[B] = new Decoder[B] {
    final def apply(c: HCursor): Decoder.Result[B] = self(c).flatMap(a => f(a)(c))

    override def tryDecode(c: ACursor): Decoder.Result[B] = {
      self.tryDecode(c).flatMap(a => f(a).tryDecode(c))
    }

    override def decodeAccumulating(c: HCursor): AccumulatingDecoder.Result[B] =
      self.decodeAccumulating(c).andThen(result => f(result).decodeAccumulating(c))
  }

Looking to this code you see that when you flatMap a decoder, you get a new decoder operating on the same cursor: the cursor is the current position of the parsing operation.

In the following code:

implicit val paperDecoder: Decoder[Paper] = {
    for {
      title <- Decoder.decodeString
      authors <- Decoder.decodeSet[PaperAuthor]
    } yield Paper(title, authors)
  }

The cursor is pointing at the beginning of the object both when you try to decode title and authors. If you do not use semi-automatic or automatic generation and you work natively with the API, you need to move the cursor yourself like so

  implicit val paperDecoder: Decoder[Paper] = Decoder.instance(cursor => Xor.right(Paper("",Set.empty)))

  implicit val paperViewDecoder: Decoder[PublishedPaperView] = Decoder.instance(
    cursor =>
      for {
        id <- cursor.downField("id").as[PaperId]
        paper <- cursor.downField("paper").as[Paper]
     } yield PublishedPaperView(id, paper)
  )