I have the following setup:
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.auto._
import io.circe.syntax._
implicit val Conf: Configuration = Configuration
.default
.withSnakeCaseConstructorNames
.withSnakeCaseMemberNames
.withDiscriminator("type")
sealed trait Generator[T]
case class UniformGenerator[T](min: T, max: T) extends Generator[T]
case class EnumGenerator[T](values: Set[T]) extends Generator[T]
sealed trait DataType
case class IntType(generator: Generator[Int]) extends DataType
case class StringType(generator: Generator[String]) extends DataType
Encoding these ADTs into JSON is working well, executing:
println((IntType(UniformGenerator(1, 5)): DataType).asJson)
prints
{
"generator" : {
"min" : 1,
"max" : 5,
"type" : "uniform_generator"
},
"type" : "int_type"
}
However, when I want to restrict the generic type for one of the generators, for instance when I require the UniformGenerator to work only with Numeric types:
case class UniformGenerator[T: Numeric](min: T, max: T) extends Generator[T]
It fails with the following error:
could not find implicit value for parameter encoder: io.circe.Encoder[DataType] println((IntType(UniformGenerator(1, 5)): DataType).asJson)
So, it can't derive the right encoder automatically. What is the reason for that and how can be fixed?
Edit: turns out this answer is wrong (or: only true for Scala 3 which the original question is not about). I made a wrong assumption about what circe does and does not support. See answer and comments by @sanyi14ka.
A) Reason(s)
To understand the reason(s), you need to understand that
is just syntactic sugar for:
So by adding the context bound, you add an additional parameter in an additional parameter list.
a) Main Reason: derivation does not support more than one parameter lists
You could try this by changing your case class definition to
That would also fail to compile (if you try to derive an
Encoderfor it) because derivation only supports case classes with one parameter list.b) Additional reason: circe would need an
Encoder[Numeric[Int]]If derivation would support additional parameter lists, circe would need an
Encoderfor the new parameter - but there is noEncoder[Numeric[Int]]in scope (nor could it be derived).B) Workarounds
As far as I am aware, there is no magic "fix" for this, but I could propose some workarounds:
a) Do not add the context bound to the case class
Depending on your use case, it might be acceptable to not add the context bound to the case class and instead add it to the functions that need it, e.g.:
b) Implement the specific
Encodermanually(the obvious solution)
c) Move the "context bound" into the main parameter list and provide an Encoder for it
You could make the third parameter explicit:
By adding another
applymethod to the companion, you could still construct it as usual:Then, you would need to somehow provide an
Once you have that, derivation of an
Encoder[UniformGenerator[Int]]should work.