Search code examples
scalagenericscirce

Circe: Generic Function that returns a specific object of a Sealed Trait (ADT)


I want to do something like this:

  def loadYaml[T <: Component](ref: LocalRef): Task[T] = {
    val yamlString = Source.fromResource(ref.url).mkString
    val json: Either[ParsingFailure, Json] = parser.parse(yamlString)
    val component = json.flatMap(_.as[T]) // this line creates the error
    Task.fromEither(component)
  }

The decoding throws:

Error:(54, 22) could not find implicit value for parameter d: io.circe.Decoder[T]
        .flatMap(_.as[T])

Component is the Sealed Trait.

Is this not possible with circe?

This works (Component instead of T):

  def loadYaml(ref: LocalRef): Task[Component] = {
    val yamlString = Source.fromResource(ref.url).mkString
    val json: Either[ParsingFailure, Json] = parser.parse(yamlString)
    val component = json.flatMap(_.as[Component]) // this line creates the error
    Task.fromEither(component)
  }

I don't think the problem is the Decoder as it works with Components, here it is anyway:

  implicit val decodeEvent: Decoder[Component] =
    List[Decoder[Component]](
      Decoder[DbConnection].widen,
      Decoder[DbLookup].widen
    ).reduceLeft(_ or _)

Solution

  • As can be seen on the documentation the as method on the Json class, requires an implicit Decoder[T].
    Since this decoder was not on the scope of the function, the compiler complied about it:

    Error:(54, 22) could not find implicit value for parameter d: io.circe.Decoder[T]
    .flatMap(_.as[T])

    The simplest solution is to just add this implicit to the method and leave the responsibility of providing it to the caller.

    def loadYaml[T <: Component](ref: LocalRef)(implicit ev: Decoder[T]): Task[T] = ...
    
    // Or even shorter (and clearer IMHO) using a context bound.
    loadYaml[T <: Component : Decoder](ref: LocalRef): Task[T] = ...