My coproduct encoding is ambiguous

Viewed 701

This question has come up a few times recently, so I'm FAQ-ing it here. Suppose I've got some case classes like this:

import io.circe._, io.circe.generic.semiauto._

object model {
  case class A(a: String)
  case class B(a: String, i: Int)
  case class C(i: Int, b: Boolean)

  implicit val encodeA: Encoder[A] = deriveEncoder
  implicit val encodeB: Encoder[B] = deriveEncoder
  implicit val encodeC: Encoder[C] = deriveEncoder
  implicit val decodeA: Decoder[A] = deriveDecoder
  implicit val decodeB: Decoder[B] = deriveDecoder
  implicit val decodeC: Decoder[C] = deriveDecoder
}

And I want to encode a value that could be any one of these as JSON using circe and Shapeless coproducts.

import io.circe.shapes._, io.circe.syntax._
import shapeless._

import model._

type ABC = A :+: B :+: C :+: CNil

val c: ABC = Coproduct[ABC](C(123, false))

This looks fine at first:

scala> c.asJson
res0: io.circe.Json =
{
  "i" : 123,
  "b" : false
}

But the problem is that I can never decode a coproduct containing a B element, since any valid JSON document that could be decoded as B can also be decoded as A, and the coproduct decoders provided by circe-shapes try the elements in the order they appear in the coproduct.

scala> val b: ABC = Coproduct[ABC](B("xyz", 123))
b: ABC = Inr(Inl(B(xyz,123)))

scala> val json = b.asJson
json: io.circe.Json =
{
  "a" : "xyz",
  "i" : 123
}

scala> io.circe.jawn.decode[ABC](json.noSpaces)
res1: Either[io.circe.Error,ABC] = Right(Inl(A(xyz)))

How can I disambiguate the elements of my coproduct in my encoding?

1 Answers
Related