Your question is similar to Implicit Json Formatter for value classes in Scala . Just there json library was play-json and you're using Circe.
deriveEncoder
and deriveDecoder
are macros (actually not themselves but they call macros). So you can't call them where you want. deriveEncoder[A]
and deriveDecoder[A]
are trying to expand where A
is not a case class or sealed trait (with case-class children) yet but an abstract type (a type parameter).
Extracting some common code to a trait and extending this trait is OOP way to avoid code duplication but macros is a different paradigm, namely metaprogramming. With metaprogramming you can not always follow OOP principles.
- If you'd like to postpone expansion of the macros you can make your implicits implicit macros. This makes difference because in your code
deriveEncoder[A]
and deriveDecoder[A]
were called (expanded) now for abstract A
but with implicit macros they will be called (expanded) later when implicits are requested i.e. when A
is inferred to be a concrete sealed trait or case class.
// in a different subproject
import io.circe.{Decoder, Encoder}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox // libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
trait SerializableTrait[A] {
implicit def someEncoder: Encoder[A] = macro SerializableTraitMacros.someEncoderImpl[A]
implicit def someDecoder: Decoder[A] = macro SerializableTraitMacros.someDecoderImpl[A]
}
class SerializableTraitMacros(val c: whitebox.Context) {
import c.universe._
val semiauto = q"_root_.io.circe.generic.semiauto"
def someEncoderImpl[A: WeakTypeTag]: Tree = q"$semiauto.deriveEncoder[${weakTypeOf[A]}]"
def someDecoderImpl[A: WeakTypeTag]: Tree = q"$semiauto.deriveDecoder[${weakTypeOf[A]}]"
}
Now you don't need to define implicits in companion objects of your case classes or sealed traits. You just need to make the companion objects extend SerializableTrait
as you wanted
import io.circe.{Decoder, Encoder}
sealed trait SomeTrait
object SomeTrait extends SerializableTrait[SomeTrait]
case class A(i: Int, s: String) extends SomeTrait // the sealed trait must have at least one case-class child
implicitly[Encoder[SomeTrait]] // compiles
implicitly[Decoder[SomeTrait]] // compiles
With scalacOptions += "-Ymacro-debug-lite"
(maybe also "-Xlog-implicits"
can be usefull) you can see what codec instances Circe actually generates for the trait
//scalac: {
// val inst$macro$14: io.circe.generic.encoding.DerivedAsObjectEncoder[App.SomeTrait] = {
// final class anon$someEncoder$macro$13 extends _root_.scala.Serializable {
// def <init>() = {
// super.<init>();
// ()
// };
// lazy val inst$macro$1: io.circe.generic.encoding.DerivedAsObjectEncoder[App.SomeTrait] = encoding.this.DerivedAsObjectEncoder.deriveEncoder[App.SomeTrait, shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out](shapeless.this.LabelledGeneric.materializeCoproduct[App.SomeTrait, (Symbol @@ String("A")) :: shapeless.HNil, App.A :+: shapeless.CNil, shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out](DefaultSymbolicLabelling.instance[App.SomeTrait, (Symbol @@ String("A")) :: shapeless.HNil](::.apply[Symbol @@ String("A"), shapeless.HNil.type](scala.Symbol.apply("A").asInstanceOf[Symbol @@ String("A")], HNil)), Generic.instance[App.SomeTrait, App.A :+: shapeless.CNil](((p: App.SomeTrait) => Coproduct.unsafeMkCoproduct((p: p: @_root_.scala.unchecked) match {
// case (_: App.A) => 0
//}, p).asInstanceOf[App.A :+: shapeless.CNil]), ((x$1: App.A :+: shapeless.CNil) => Coproduct.unsafeGet(x$1).asInstanceOf[App.SomeTrait])), coproduct.this.ZipWithKeys.cpZipWithKeys[Symbol @@ String("A"), App.A, shapeless.HNil, shapeless.CNil](coproduct.this.ZipWithKeys.cnilZipWithKeys, Witness.mkWitness[Symbol with shapeless.tag.Tagged[String("A")]](scala.Symbol.apply("A").asInstanceOf[Symbol @@ String("A")].asInstanceOf[Symbol with shapeless.tag.Tagged[String("A")]])), scala.this.<:<.refl[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out]), shapeless.Lazy.apply[io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out]](inst$macro$2)).asInstanceOf[io.circe.generic.encoding.DerivedAsObjectEncoder[App.SomeTrait]];
// lazy val inst$macro$2: io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out] = ({
// final class $anon extends io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out] {
// def <init>() = {
// super.<init>();
// ()
// };
// private[this] val circeGenericEncoderForA = shapeless.Lazy.apply[io.circe.generic.encoding.DerivedAsObjectEncoder[App.A]](inst$macro$3).value;
// final def encodeObject(a: shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out): io.circe.JsonObject = shapeless.Inr.apply[Nothing, shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out](a) match {
// case shapeless.Inr((circeGenericInrBindingForA @ _)) => circeGenericInrBindingForA match {
// case shapeless.Inl((circeGenericInlBindingForA @ _)) => io.circe.JsonObject.singleton("A", $anon.this.circeGenericEncoderForA.apply(circeGenericInlBindingForA))
// case shapeless.Inr(_) => scala.sys.`package`.error("Cannot encode CNil")
// }
// }
// };
// new $anon()
//}: io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out]).asInstanceOf[io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out]];
// lazy val inst$macro$3: io.circe.generic.encoding.DerivedAsObjectEncoder[App.A] = encoding.this.DerivedAsObjectEncoder.deriveEncoder[App.A, shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](shapeless.this.LabelledGeneric.materializeProduct[App.A, (Symbol @@ String("i")) :: (Symbol @@ String("s")) :: shapeless.HNil, Int :: String :: shapeless.HNil, shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](DefaultSymbolicLabelling.instance[App.A, (Symbol @@ String("i")) :: (Symbol @@ String("s")) :: shapeless.HNil](::.apply[Symbol @@ String("i"), (Symbol @@ String("s")) :: shapeless.HNil.type](scala.Symbol.apply("i").asInstanceOf[Symbol @@ String("i")], ::.apply[Symbol @@ String("s"), shapeless.HNil.type](scala.Symbol.apply("s").asInstanceOf[Symbol @@ String("s")], HNil))), Generic.instance[App.A, Int :: String :: shapeless.HNil](((x0$3: App.A) => x0$3 match {
// case App.this.A((i$macro$10 @ _), (s$macro$11 @ _)) => ::.apply[Int, String :: shapeless.HNil.type](i$macro$10, ::.apply[String, shapeless.HNil.type](s$macro$11, HNil)).asInstanceOf[Int :: String :: shapeless.HNil]
//}), ((x0$4: Int :: String :: shapeless.HNil) => x0$4 match {
// case ::((i$macro$8 @ _), ::((s$macro$9 @ _), HNil)) => App.this.A.apply(i$macro$8, s$macro$9)
//})), hlist.this.ZipWithKeys.hconsZipWithKeys[Symbol @@ String("i"), Int, (Symbol @@ String("s")) :: shapeless.HNil, String :: shapeless.HNil, shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](hlist.this.ZipWithKeys.hconsZipWithKeys[Symbol @@ String("s"), String, shapeless.HNil, shapeless.HNil, shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](hlist.this.ZipWithKeys.hnilZipWithKeys, Witness.mkWitness[Symbol with shapeless.tag.Tagged[String("s")]](scala.Symbol.apply("s").asInstanceOf[Symbol @@ String("s")].asInstanceOf[Symbol with shapeless.tag.Tagged[String("s")]])), Witness.mkWitness[Symbol with shapeless.tag.Tagged[String("i")]](scala.Symbol.apply("i").asInstanceOf[Symbol @@ String("i")].asInstanceOf[Symbol with shapeless.tag.Tagged[String("i")]])), scala.this.<:<.refl[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]), shapeless.Lazy.apply[io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]](inst$macro$12)).asInstanceOf[io.circe.generic.encoding.DerivedAsObjectEncoder[App.A]];
// lazy val inst$macro$12: io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] = ({
// final class $anon extends io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] {
// def <init>() = {
// super.<init>();
// ()
// };
// private[this] val circeGenericEncoderFori = circe.this.Encoder.encodeInt;
// private[this] val circeGenericEncoderFors = circe.this.Encoder.encodeString;
// final def encodeObject(a: shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out): io.circe.JsonObject = a match {
// case shapeless.::((circeGenericHListBindingFori @ _), shapeless.::((circeGenericHListBindingFors @ _), shapeless.HNil)) => io.circe.JsonObject.fromIterable(scala.collection.immutable.Vector.apply[(String, io.circe.Json)](scala.Tuple2.apply[String, io.circe.Json]("i", $anon.this.circeGenericEncoderFori.apply(circeGenericHListBindingFori)), scala.Tuple2.apply[String, io.circe.Json]("s", $anon.this.circeGenericEncoderFors.apply(circeGenericHListBindingFors))))
// }
// };
// new $anon()
//}: io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]).asInstanceOf[io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]]
// };
// new anon$someEncoder$macro$13().inst$macro$1
// };
// _root_.shapeless.Lazy.apply[io.circe.generic.encoding.DerivedAsObjectEncoder[App.SomeTrait]](inst$macro$14)
//}
//scalac: {
// val inst$macro$14: io.circe.generic.decoding.DerivedDecoder[App.SomeTrait] = {
// final class anon$someDecoder$macro$13 extends _root_.scala.Serializable {
// def <init>() = {
// super.<init>();
// ()
// };
// lazy val inst$macro$1: io.circe.generic.decoding.DerivedDecoder[App.SomeTrait] = decoding.this.DerivedDecoder.deriveDecoder[App.SomeTrait, shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out](shapeless.this.LabelledGeneric.materializeCoproduct[App.SomeTrait, (Symbol @@ String("A")) :: shapeless.HNil, App.A :+: shapeless.CNil, shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out](DefaultSymbolicLabelling.instance[App.SomeTrait, (Symbol @@ String("A")) :: shapeless.HNil](::.apply[Symbol @@ String("A"), shapeless.HNil.type](scala.Symbol.apply("A").asInstanceOf[Symbol @@ String("A")], HNil)), Generic.instance[App.SomeTrait, App.A :+: shapeless.CNil](((p: App.SomeTrait) => Coproduct.unsafeMkCoproduct((p: p: @_root_.scala.unchecked) match {
// case (_: App.A) => 0
//}, p).asInstanceOf[App.A :+: shapeless.CNil]), ((x$1: App.A :+: shapeless.CNil) => Coproduct.unsafeGet(x$1).asInstanceOf[App.SomeTrait])), coproduct.this.ZipWithKeys.cpZipWithKeys[Symbol @@ String("A"), App.A, shapeless.HNil, shapeless.CNil](coproduct.this.ZipWithKeys.cnilZipWithKeys, Witness.mkWitness[Symbol with shapeless.tag.Tagged[String("A")]](scala.Symbol.apply("A").asInstanceOf[Symbol @@ String("A")].asInstanceOf[Symbol with shapeless.tag.Tagged[String("A")]])), scala.this.<:<.refl[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out]), shapeless.Lazy.apply[io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out]](inst$macro$2)).asInstanceOf[io.circe.generic.decoding.DerivedDecoder[App.SomeTrait]];
// lazy val inst$macro$2: io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out] = ({
// final class $anon extends io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out] {
// def <init>() = {
// super.<init>();
// ()
// };
// private[this] val circeGenericDecoderForA = shapeless.Lazy.apply[io.circe.generic.decoding.DerivedDecoder[App.A]](inst$macro$3).value;
// final def apply(c: io.circe.HCursor): io.circe.Decoder.Result[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out] = {
// val result = c.downField("A");
// if (result.succeeded)
// scala.Some.apply[io.circe.Decoder.Result[App.A]]($anon.this.circeGenericDecoderForA.tryDecode(result))
// else
// scala.None
// } match {
// case scala.Some((result @ _)) => result match {
// case scala.util.Right((v @ _)) => scala.util.Right.apply[Nothing, shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out](ReprDecoder.injectLeftValue[Symbol @@ String("A"), App.A, shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out](v))
// case scala.util.Left((err @ _)) => scala.util.Left.apply[io.circe.DecodingFailure, Nothing](err)
// }
// case scala.None => (scala.util.Left.apply[io.circe.DecodingFailure, shapeless.CNil](io.circe.DecodingFailure.apply("JSON decoding to CNil should never happen", c.history)): scala.util.Either[io.circe.DecodingFailure, shapeless.CNil]) match {
// case scala.util.Right((v @ _)) => scala.util.Right.apply[Nothing, shapeless.Inr[Nothing,shapeless.CNil]](shapeless.Inr.apply[Nothing, shapeless.CNil](v))
// case scala.util.Left((err @ _)) => scala.util.Left.apply[io.circe.DecodingFailure, Nothing](err)
// }
// };
// final override def decodeAccumulating(c: io.circe.HCursor): io.circe.Decoder.AccumulatingResult[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out] = {
// val result = c.downField("A");
// if (result.succeeded)
// scala.Some.apply[io.circe.Decoder.AccumulatingResult[App.A]]($anon.this.circeGenericDecoderForA.tryDecodeAccumulating(result))
// else
// scala.None
// } match {
// case scala.Some((result @ _)) => result.map[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out](((v: App.A) => ReprDecoder.injectLeftValue[Symbol @@ String("A"), App.A, shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out](v)))
// case scala.None => cats.data.Validated.invalidNel[io.circe.DecodingFailure, shapeless.CNil](io.circe.DecodingFailure.apply("JSON decoding to CNil should never happen", c.history)).map[shapeless.Inr[Nothing,shapeless.CNil]](((x$3: shapeless.CNil) => shapeless.Inr.apply[Nothing, shapeless.CNil](x$3)))
// }
// };
// new $anon()
//}: io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out]).asInstanceOf[io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("A"),App.A] :+: shapeless.ops.coproduct.ZipWithKeys.cnilZipWithKeys.Out]];
// lazy val inst$macro$3: io.circe.generic.decoding.DerivedDecoder[App.A] = decoding.this.DerivedDecoder.deriveDecoder[App.A, shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](shapeless.this.LabelledGeneric.materializeProduct[App.A, (Symbol @@ String("i")) :: (Symbol @@ String("s")) :: shapeless.HNil, Int :: String :: shapeless.HNil, shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](DefaultSymbolicLabelling.instance[App.A, (Symbol @@ String("i")) :: (Symbol @@ String("s")) :: shapeless.HNil](::.apply[Symbol @@ String("i"), (Symbol @@ String("s")) :: shapeless.HNil.type](scala.Symbol.apply("i").asInstanceOf[Symbol @@ String("i")], ::.apply[Symbol @@ String("s"), shapeless.HNil.type](scala.Symbol.apply("s").asInstanceOf[Symbol @@ String("s")], HNil))), Generic.instance[App.A, Int :: String :: shapeless.HNil](((x0$3: App.A) => x0$3 match {
// case App.this.A((i$macro$10 @ _), (s$macro$11 @ _)) => ::.apply[Int, String :: shapeless.HNil.type](i$macro$10, ::.apply[String, shapeless.HNil.type](s$macro$11, HNil)).asInstanceOf[Int :: String :: shapeless.HNil]
//}), ((x0$4: Int :: String :: shapeless.HNil) => x0$4 match {
// case ::((i$macro$8 @ _), ::((s$macro$9 @ _), HNil)) => App.this.A.apply(i$macro$8, s$macro$9)
//})), hlist.this.ZipWithKeys.hconsZipWithKeys[Symbol @@ String("i"), Int, (Symbol @@ String("s")) :: shapeless.HNil, String :: shapeless.HNil, shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](hlist.this.ZipWithKeys.hconsZipWithKeys[Symbol @@ String("s"), String, shapeless.HNil, shapeless.HNil, shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](hlist.this.ZipWithKeys.hnilZipWithKeys, Witness.mkWitness[Symbol with shapeless.tag.Tagged[String("s")]](scala.Symbol.apply("s").asInstanceOf[Symbol @@ String("s")].asInstanceOf[Symbol with shapeless.tag.Tagged[String("s")]])), Witness.mkWitness[Symbol with shapeless.tag.Tagged[String("i")]](scala.Symbol.apply("i").asInstanceOf[Symbol @@ String("i")].asInstanceOf[Symbol with shapeless.tag.Tagged[String("i")]])), scala.this.<:<.refl[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]), shapeless.Lazy.apply[io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]](inst$macro$12)).asInstanceOf[io.circe.generic.decoding.DerivedDecoder[App.A]];
// lazy val inst$macro$12: io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] = ({
// final class $anon extends io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] {
// def <init>() = {
// super.<init>();
// ()
// };
// private[this] val circeGenericDecoderFori = circe.this.Decoder.decodeInt;
// private[this] val circeGenericDecoderFors = circe.this.Decoder.decodeString;
// final def apply(c: io.circe.HCursor): io.circe.Decoder.Result[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] = ReprDecoder.consResults[io.circe.Decoder.Result, Symbol @@ String("i"), Int, shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]($anon.this.circeGenericDecoderFori.tryDecode(c.downField("i")), ReprDecoder.consResults[io.circe.Decoder.Result, Symbol @@ String("s"), String, shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]($anon.this.circeGenericDecoderFors.tryDecode(c.downField("s")), ReprDecoder.hnilResult)(io.circe.Decoder.resultInstance))(io.circe.Decoder.resultInstance);
// final override def decodeAccumulating(c: io.circe.HCursor): io.circe.Decoder.AccumulatingResult[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] = ReprDecoder.consResults[io.circe.Decoder.AccumulatingResult, Symbol @@ String("i"), Int, shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]($anon.this.circeGenericDecoderFori.tryDecodeAccumulating(c.downField("i")), ReprDecoder.consResults[io.circe.Decoder.AccumulatingResult, Symbol @@ String("s"), String, shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]($anon.this.circeGenericDecoderFors.tryDecodeAccumulating(c.downField("s")), ReprDecoder.hnilResultAccumulating)(io.circe.Decoder.accumulatingResultInstance))(io.circe.Decoder.accumulatingResultInstance)
// };
// new $anon()
//}: io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]).asInstanceOf[io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("i"),Int] :: shapeless.labelled.FieldType[Symbol @@ String("s"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]]
// };
// new anon$someDecoder$macro$13().inst$macro$1
// };
// _root_.shapeless.Lazy.apply[io.circe.generic.decoding.DerivedDecoder[App.SomeTrait]](inst$macro$14)
//}
If you want implicitly[Encoder[A]]
, implicitly[Decoder[A]]
to compile too you should define companion object A extends SerializableTrait[A]
. So it's better to call SerializableTrait
differently, SerializableTraitOrClass
or something like that.
- Alternatively, you can define macro annotation for classes/traits or their companion objects. It will generate necessary implicits inside companion objects
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.reflect.macros.blackbox
import scala.language.experimental.macros
@compileTimeOnly("""enable macro annotations: scalacOptions += "-Ymacro-annotations" """)
class serializableTrait extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro SerializableTraitMacros.macroTransformImpl
}
object SerializableTraitMacros {
def macroTransformImpl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
val circe = q"_root_.io.circe"
val semiauto = q"$circe.generic.semiauto"
def modifyObject(obj: Tree): Tree = obj match {
case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" =>
val className = tname.toTypeName
val encoder = TermName(c.freshName(s"${tname}Encoder"))
val decoder = TermName(c.freshName(s"${tname}Decoder"))
q"""$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
..$body
implicit val $encoder: $circe.Encoder[$className] = $semiauto.deriveEncoder[$className]
implicit val $decoder: $circe.Decoder[$className] = $semiauto.deriveDecoder[$className]
}"""
}
def modify(cls: Tree, obj: Tree): Tree = q"..${Seq(cls, modifyObject(obj))}"
annottees match {
case (cls: ClassDef) :: (obj: ModuleDef) :: Nil => modify(cls, obj)
case (cls: ClassDef) :: Nil => modify(cls, q"object ${cls.name.toTermName}")
case (obj: ModuleDef) :: Nil => modifyObject(obj) // this works for the companion object of a sealed trait or top-level case class but not nested case class
}
}
}
import io.circe.{Decoder, Encoder}
@serializableTrait
sealed trait SomeTrait
case class A(i: Int, s: String) extends SomeTrait
implicitly[Encoder[SomeTrait]] // compiles
implicitly[Decoder[SomeTrait]] // compiles
//scalac: object SomeTrait extends scala.AnyRef {
// def <init>() = {
// super.<init>();
// ()
// };
// implicit val SomeTraitEncoder$macro$1: _root_.io.circe.Encoder[SomeTrait] = _root_.io.circe.generic.semiauto.deriveEncoder[SomeTrait];
// implicit val SomeTraitDecoder$macro$2: _root_.io.circe.Decoder[SomeTrait] = _root_.io.circe.generic.semiauto.deriveDecoder[SomeTrait]
//}
If you want implicitly[Encoder[A]]
, implicitly[Decoder[A]]
to compile too you should annotate case class A
too.
The macro annotation is implemented now so that you can annotate either trait or its companion object (anyway the implicits will be generated in the companion object) but for some reason this doesn't work if you annotate the companion object of a case class (could not find Lazy implicit value of type io.circe.generic.encoding.DerivedAsObjectEncoder[A]
). This seems to happen because I tested for nested SomeTrait
and A
, for top-level it's ok.
Actually, our macro annotation is similar to standard @JsonCodec
https://circe.github.io/circe/codecs/semiauto-derivation.html#jsoncodec
import io.circe.generic.JsonCodec
@JsonCodec
sealed trait SomeTrait
@JsonCodec
case class A(i: Int, s: String) extends SomeTrait
// object SomeTrait extends scala.AnyRef {
// def <init>() = {
// super.<init>();
// ()
// };
// implicit val codecForSomeTrait: AsObject[SomeTrait] = semiauto.deriveCodec[SomeTrait]
// };
// object A extends scala.AnyRef {
// def <init>() = {
// super.<init>();
// ()
// };
// implicit val codecForA: AsObject[A] = semiauto.deriveCodec[A]
// };
https://github.com/milessabin/shapeless/issues/1287
https://github.com/milessabin/shapeless/pull/1286