Decoder.decodeAccumulating discards errors in ADTs
morgen-peschke opened this issue · comments
Morgen Peschke commented
The microsite shows the standard way to write a Decoder
for an ADT is to merge a list of Decoder
s 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)
}