Reputation: 1056
I have to consume a json api, where there is a list of discrete string values in snake case.
The example below works, but I would like to remove the manual fooDecoder
in favor of the (currently commented out) ConfiguredJsonCodec annotation.
Or more fundamental question: Does modeling these discrete values as case objects in an ADT even makes sense, or is there another approach?
import io.circe._
import io.circe.syntax._
import io.circe.generic.extras.{Configuration, ConfiguredJsonCodec, JsonKey}
import io.circe.parser.parse
implicit val jsonConfig: Configuration = Configuration.default
.withSnakeCaseConstructorNames
.withSnakeCaseMemberNames
//@ConfiguredJsonCodec(decodeOnly = true)
sealed trait Foo
object Foo {
case object FooBar extends Foo
case object FooBaz extends Foo
case object FooWuz extends Foo
}
import Foo._
implicit val fooDecoder: Decoder[Foo] = new Decoder[Foo] {
override def apply(c: HCursor) = c.as[String].map{
case "foo_bar" => FooBar
case "foo_baz" => FooBaz
case "foo_wuz" => FooWuz
}
}
@ConfiguredJsonCodec(decodeOnly = true)
case class Qux(fooFoo: List[Foo])
val input ="""{"foo_foo" : ["foo_bar", "foo_baz", "foo_wuz"]}"""
val json: Json = parse(input).left.map(println(_)).right.get
json.as[Qux]
Complete example: https://scastie.scala-lang.org/eVFyNMGFRgaw9oEkRveT8g
This uses circe 0.13.0
Upvotes: 2
Views: 789
Reputation: 27535
All Circe derivation methods for coproducts assume that there is discrimination field and at best you can pick its name. So the supported format is something like:
{
"type": "foo_bar",
}
instead of just
"foo_bar"
The reason is that derivation mechanism sees no difference between case-object-only and case-object-and-case-classes, so for code like this
trait MyADT
object MyADT {
case class Value1(value: String) extends MyADT
case class Value2(value: String) extends MyADT
}
where you simply would combine codecs of sum's parts
val decoder: Decoder[MyADT] = Decoder[Value1].widen orElse Decoder[Value2].widen
you would always decode to first Value1
even if you encoded Value2
. Derivation sees no difference between these two cases and so they prefer to be on the safe side always by using discrimination field. (This is discussed in docs).
However, if you know that you are building enum, you can simply use enumeratum with circe support:
import enumeratum._
sealed trait Foo extends EnumEntry with Snakecase
object Foo extends Enum[Foo] with CirceEnum[Foo] {
case object FooBar extends Foo
case object FooBaz extends Foo
case object FooWuz extends Foo
val values = findValues
}
This would achieve the very goal you aim for, although in a different way.
Upvotes: 2