Search code examples
jsonscalacirce

Extending AutoDerivation in Circe does not work


My question concerns the second solution offered by mixel here: Scala Circe with generics

Note that the trait named Auto in Circe has been renamed to AutoDerivation in the current version of Circe.

I am using the solution mixel provides in his StackOverflow solution but have not been able to get it to work. I have tried things like updating my Circe version to the most recent one and making sure the Macro Paradise plugin is imported, but still no luck.

Here is my code. The first is its own file, called CirceGeneric.

import io.circe._
import io.circe.parser._
import io.circe.generic.extras._

object CirceGeneric {
  trait JsonEncoder[T] {
    def apply(in: T): Json
  }

  trait JsonDecoder[T] {
    def apply(s: String): Either[Error, T]
  }

  object CirceEncoderProvider {
    def apply[T: Encoder]: JsonEncoder[T] = new JsonEncoder[T] {
      def apply(in: T) = Encoder[T].apply(in)
    }
  }

  object CirceDecoderProvider {
    def apply[T: Decoder]: JsonDecoder[T] = new JsonDecoder[T] {
      def apply(s: String) = decode[T](s)
    }
  }
}

object Generic extends AutoDerivation {
  import CirceGeneric._

  implicit def encoder[T: Encoder]: JsonEncoder[T] = CirceEncoderProvider[T]
  implicit def decoder[T: Decoder]: JsonDecoder[T] = CirceDecoderProvider[T]

}

The second is a method for unit testing that uses the Akka function responseAs. The method appears in a class called BaseServiceTest.

  def responseTo[T]: T = {
    def response(s: String)(implicit d: JsonDecoder[T]) = {
      d.apply(responseAs[String]) match {
        case Right(value) => value
        case Left(error) => throw new IllegalArgumentException(error.fillInStackTrace)
      }
    }
    response(responseAs[String])
  }

The idea is to convert the result of responseAs[String] (which returns a string) into a decoded case class.

The code is not behaving as expected. Intellij does not detect any missing implicits, but when compilation time comes around, I am getting problems. I should mention that the BaseServiceTest file contains imports for CirceGeneric._ and Generic._, so a missing import statement is not the problem.

[error] [...]/BaseServiceTest.scala:59: could not find implicit value for parameter d: [...]CirceGeneric.JsonDecoder[T] [error] response(responseAs[String])

Either the implicit conversion from Decoder[T] to JsonDecoder[T] is not happening, or the Decoder[T] instance is not being created. Either way, something is wrong.


Solution

  • You still need a Decoder or JsonDecoder context bound on responseTo.

    def responseTo[T : Decoder]: T = ...
    

    This is because all your code, and indeed mixel's code in the linked answer, is about abstracting from a Decoder out to a JsonDecoder trait which can be used for cross-library support. But you still don't have any way of constructing one without an underlying Decoder instance.

    Now, there are some ways of automatically generating Decoders for (for instance) case classes contained in circe.generics.auto, but at this point in your code

    def responseTo[T]: T = {
        def response(s: String)(implicit d: JsonDecoder[T]) = ...
        ...
    }
    

    you're asking the compiler to be able to provide an implicit JsonDecoder (i.e., in your setup, Decoder) instance for any arbitrary type. As the accepted answer to the linked question explains, that's not possible.

    You need to delay the implicit resolution to the point where you know what type you're dealing with - in particular, that you can provide a Decoder[T] instance for it.

    EDIT: In your response to your comment regarding what the point is if you can't create JsonDecoders for all types...

    My understanding of the linked question is that they're trying to abstract away the circe library in order to allow swapping out the JSON library implementation. This is done as follows:

    • add the JsonDecoder type class

    • have a package json which contains implicits (using Circe) for constructing them automatically via the package object extending AutoDerivation

    • have external code only refer to JsonDecoder and import the implicits in the json package

    Then all the JSON serialization and implicit resolution works out without ever needing the calling code to reference io.circe, and it's easy to switch over the json/JsonDecoder to another JSON library if desired. But you're still going to have to use the JsonDecoder context bound, and be restricted to working with types where such an implicit can be constructed. Which is not every type.