circe / circe

Yet another JSON library for Scala

Home Page:https://circe.github.io/circe/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Decoder.decodeAccumulating discards errors in ADTs

morgen-peschke opened this issue · comments

The microsite shows the standard way to write a Decoder for an ADT is to merge a list of Decoders using Decoder#or:

implicit val decodeEvent: Decoder[Event] =
  List[Decoder[Event]](
    Decoder[Foo].widen,
    Decoder[Bar].widen,
    Decoder[Baz].widen,
    Decoder[Qux].widen
  ).reduceLeft(_ or _)

This works when the JSON is correct, and produces misleading errors when the JSON structure is incorrect:

println(decode[Event]("""{ "i": null }""").show)
// Left(DecodingFailure at .values: Missing required field)

Which makes sense, as decode returns as single error. What makes less sense is that decodeAccumulating returns almost exactly the same thing:

println(decodeAccumulating[Event]("""{ "i": null }""").show)
// Invalid(NonEmptyList(DecodingFailure at .values: Missing required field))

A more useful error from decodeAccumulating could look like this:

Invalid(NonEmptyList(DecodingFailure at .i: Int, DecodingFailure at .s: Missing required field, DecodingFailure at .c: Missing required field, DecodingFailure at .values: Missing required field))

It looks like this could be accomplished with a change to Decoder#or (and likely a similar change to Decoder#either, for consistency):

// Existing
override def tryDecodeAccumulating(c: ACursor): Decoder.AccumulatingResult[AA] =
  self.tryDecodeAccumulating(c) match {
    case r @ Valid(_) => r
    case Invalid(_)   => d.tryDecodeAccumulating(c)
  }
// Proposed
override def tryDecodeAccumulating(c: ACursor): Decoder.AccumulatingResult[AA] =
  self.tryDecodeAccumulating(c) match {
    case r @ Valid(_) => r
    case Invalid(i)   => d.tryDecodeAccumulating(c).leftMap(i.concatNel)
  }

Scastie with the above implemented as a Semigroup