├── .gitignore ├── README.md ├── bench └── src │ └── main │ └── scala │ └── io │ └── circe │ └── examples │ └── derivation │ └── bench │ └── Bench.scala ├── build.sbt ├── core └── src │ └── main │ └── scala │ └── io │ └── circe │ └── examples │ └── derivation │ ├── better │ ├── DerivedDecoder.scala │ ├── DerivedObjectEncoder.scala │ ├── ExportHookAdapter.scala │ └── auto.scala │ ├── dryer │ ├── decoders.scala │ └── encoders.scala │ ├── raw │ ├── DerivationMacros.scala │ └── package.scala │ └── simple │ ├── CoproductCodecs.scala │ ├── HListCodecs.scala │ └── package.scala └── project ├── Boilerplate.scala └── build.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | target/ 3 | .idea/ 4 | .idea_modules/ 5 | .DS_STORE 6 | .cache 7 | .settings 8 | .project 9 | .classpath 10 | tmp/ 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generic derivation for circe 2 | 3 | This project contains four implementations of generic derivation for [circe](https://github.com/travisbrown/circe): 4 | 5 | * `raw`: A macro implementation (based on Argonaut's derivation support) that doesn't depend on Shapeless and provides a limited range of features. 6 | * `simple`: A straightforward Shapeless-based implementation that doesn't work for some corner cases. 7 | * `dryer`: An even more elegant implementation that uses Shapeless's `LabelledTypeClass`. 8 | * `better`: An improved version of `simple` with more complete support and more reasonable prioritization. 9 | 10 | `better` is close to equivalent to circe-generic before the 0.4.0 release. 11 | 12 | These are provided primarily for pedagogical purposes, but can also be useful for comparing the 13 | complexity or performance of the different approaches. 14 | 15 | ## License 16 | 17 | These examples are licensed under the **[Apache License, Version 2.0][apache]** 18 | (the "License"); you may not use this software except in compliance with the 19 | License. 20 | 21 | Unless required by applicable law or agreed to in writing, software 22 | distributed under the License is distributed on an "AS IS" BASIS, 23 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | See the License for the specific language governing permissions and 25 | limitations under the License. 26 | 27 | [apache]: http://www.apache.org/licenses/LICENSE-2.0 28 | -------------------------------------------------------------------------------- /bench/src/main/scala/io/circe/examples/derivation/bench/Bench.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.bench 2 | 3 | import java.io.{ File, FileWriter, PrintWriter } 4 | import scala.concurrent.duration.Duration 5 | 6 | abstract class Bench(name: String) { 7 | def ccResults: List[Duration] 8 | def adtResults: List[Duration] 9 | } 10 | 11 | object Bench { 12 | def main(args: Array[String]): Unit = { 13 | val writer = new PrintWriter(new FileWriter(new File(args(0)))) 14 | 15 | writer.println("time,deriver,size") 16 | 17 | Raw.ccResults.zipWithIndex.foreach { 18 | case (result, i) => 19 | writer.println(s"${ result.toMillis }, raw macros, ${ i % math.min(Raw.size + 1, 23) }") 20 | } 21 | 22 | Simple.ccResults.zipWithIndex.foreach { 23 | case (result, i) => writer.println(s"${ result.toMillis }, simple Shapeless, ${ i % (Simple.size + 1) }") 24 | } 25 | 26 | /*Dryer.ccResults.zipWithIndex.foreach { 27 | case (result, i) => writer.println(s"${ result.toMillis }, D, ${ i % (Dryer.size + 1) }") 28 | }*/ 29 | 30 | Better.ccResults.zipWithIndex.foreach { 31 | case (result, i) => writer.println(s"${ result.toMillis }, better Shapeless, ${ i % (Better.size + 1) }") 32 | } 33 | 34 | Generic.ccResults.zipWithIndex.foreach { 35 | case (result, i) => writer.println(s"${ result.toMillis }, circe-generic, ${ i % (Generic.size + 1) }") 36 | } 37 | 38 | writer.close() 39 | 40 | val adtWriter = new PrintWriter(new FileWriter(new File(args(1)))) 41 | 42 | adtWriter.println("time,deriver,size") 43 | 44 | Simple.adtResults.zipWithIndex.foreach { 45 | case (result, i) => adtWriter.println(s"${ result.toMillis }, simple Shapeless, ${ i % Simple.size + 1}") 46 | } 47 | 48 | /*Dryer.adtResults.zipWithIndex.foreach { 49 | case (result, i) => adtWriter.println(s"${ result.toMillis }, D, ${ i % Dryer.size + 1 }") 50 | }*/ 51 | 52 | Better.adtResults.zipWithIndex.foreach { 53 | case (result, i) => adtWriter.println(s"${ result.toMillis }, better Shapeless, ${ i % Better.size + 1 }") 54 | } 55 | 56 | Generic.adtResults.zipWithIndex.foreach { 57 | case (result, i) => adtWriter.println(s"${ result.toMillis }, circe-generic, ${ i % Generic.size + 1 }") 58 | } 59 | 60 | adtWriter.close() 61 | 62 | 63 | val deepWriter = new PrintWriter(new FileWriter(new File(args(2)))) 64 | 65 | deepWriter.println("time,deriver,depth") 66 | 67 | Raw.deepResults.zipWithIndex.foreach { 68 | case (result, i) => deepWriter.println(s"${ result.toMillis }, raw macros, ${ i % (Raw.size + 1) }") 69 | } 70 | 71 | Simple.deepResults.zipWithIndex.foreach { 72 | case (result, i) => deepWriter.println(s"${ result.toMillis }, simple Shapeless, ${ i % (Simple.size + 1) }") 73 | } 74 | 75 | /*Dryer.deepResults.zipWithIndex.foreach { 76 | case (result, i) => deepWriter.println(s"${ result.toMillis }, D, ${ i % Dryer.size + 1 }") 77 | }*/ 78 | 79 | Better.deepResults.zipWithIndex.foreach { 80 | case (result, i) => deepWriter.println(s"${ result.toMillis }, better Shapeless, ${ i % (Better.size + 1) }") 81 | } 82 | 83 | Generic.deepResults.zipWithIndex.foreach { 84 | case (result, i) => deepWriter.println(s"${ result.toMillis }, circe-generic, ${ i % (Generic.size + 1) }") 85 | } 86 | 87 | deepWriter.close() 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val catsVersion = "0.5.1" 2 | val benchSize = 1 3 | val benchReps = 1 4 | 5 | val buildSettings = Seq( 6 | organization := "io.circe", 7 | scalaVersion := "2.11.8", 8 | scalacOptions ++= Seq( 9 | "-feature", 10 | "-language:higherKinds", 11 | "-Ywarn-unused-import", 12 | "-Xfuture" 13 | ), 14 | scalacOptions in (Compile, console) ~= { 15 | _.filterNot(Set("-Ywarn-unused-import")) 16 | }, 17 | autoAPIMappings := true, 18 | libraryDependencies ++= Seq( 19 | "io.circe" %% "circe-core" % catsVersion, 20 | "io.circe" %% "circe-jawn" % catsVersion, 21 | "io.circe" %% "circe-literal" % catsVersion, 22 | "com.chuusai" %% "shapeless" % "2.3.2", 23 | "org.typelevel" %% "macro-compat" % "1.1.1", 24 | "org.typelevel" %% "export-hook" % "1.1.0", 25 | compilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full) 26 | ) 27 | ) 28 | 29 | val core = project.settings(buildSettings) 30 | val definitions = project.settings(buildSettings).settings( 31 | sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.ccs(benchSize)), 32 | sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.adts(benchSize)), 33 | sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.deeps(benchSize)) 34 | ) 35 | 36 | val bench = project.settings(buildSettings).settings( 37 | libraryDependencies ++= Seq( 38 | "io.circe" %% "circe-generic" % catsVersion, 39 | "org.scalacheck" %% "scalacheck" % "1.13.2" 40 | ), 41 | sourceGenerators in Compile <+= (sourceManaged in Compile).map( 42 | Boilerplate.benches(benchSize, benchReps) 43 | ) 44 | ).dependsOn(core, definitions) 45 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/better/DerivedDecoder.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.better 2 | 3 | import cats.data.Xor 4 | import export.exports 5 | import io.circe.{ Decoder, DecodingFailure, HCursor } 6 | import shapeless.{ ::, :+:, CNil, Coproduct, HList, HNil, Inl, Inr, LabelledGeneric, Lazy, Witness } 7 | import shapeless.labelled.{ FieldType, field } 8 | 9 | abstract class DerivedDecoder[A] extends Decoder[A] 10 | 11 | @exports 12 | final object DerivedDecoder extends LowPriorityDerivedDecoders { 13 | implicit val decodeHNil: DerivedDecoder[HNil] = new DerivedDecoder[HNil] { 14 | def apply(c: HCursor): Decoder.Result[HNil] = Xor.right(HNil) 15 | } 16 | 17 | implicit def decodeHCons[K <: Symbol, H, T <: HList](implicit 18 | key: Witness.Aux[K], 19 | decodeH: Lazy[Decoder[H]], 20 | decodeT: Lazy[DerivedDecoder[T]] 21 | ): DerivedDecoder[FieldType[K, H] :: T] = new DerivedDecoder[FieldType[K, H] :: T] { 22 | def apply(c: HCursor): Decoder.Result[FieldType[K, H] :: T] = for { 23 | h <- c.get(key.value.name)(decodeH.value) 24 | t <- decodeT.value(c) 25 | } yield field[K](h) :: t 26 | } 27 | 28 | implicit val decodeCNil: DerivedDecoder[CNil] = new DerivedDecoder[CNil] { 29 | def apply(c: HCursor): Decoder.Result[CNil] = Xor.left(DecodingFailure("CNil", c.history)) 30 | } 31 | 32 | implicit def decodeCoproduct[K <: Symbol, L, R <: Coproduct](implicit 33 | key: Witness.Aux[K], 34 | decodeL: Lazy[Decoder[L]], 35 | decodeR: Lazy[DerivedDecoder[R]] 36 | ): DerivedDecoder[FieldType[K, L] :+: R] = new DerivedDecoder[FieldType[K, L] :+: R] { 37 | def apply(c: HCursor): Decoder.Result[FieldType[K, L] :+: R] = 38 | c.downField(key.value.name).focus match { 39 | case Some(value) => value.as(decodeL.value).map(l => Inl(field(l))) 40 | case None => decodeR.value(c).map(Inr(_)) 41 | } 42 | } 43 | 44 | implicit def decodeCaseClass[A, R <: HList](implicit 45 | gen: LabelledGeneric.Aux[A, R], 46 | decodeR: Lazy[DerivedDecoder[R]] 47 | ): DerivedDecoder[A] = new DerivedDecoder[A] { 48 | def apply(c: HCursor): Decoder.Result[A] = decodeR.value(c).map(gen.from) 49 | } 50 | 51 | implicit def decodeAdt[A, R <: Coproduct](implicit 52 | gen: LabelledGeneric.Aux[A, R], 53 | decodeR: Lazy[DerivedDecoder[R]] 54 | ): DerivedDecoder[A] = new DerivedDecoder[A] { 55 | def apply(c: HCursor): Decoder.Result[A] = decodeR.value(c).map(gen.from) 56 | } 57 | } 58 | 59 | trait LowPriorityDerivedDecoders { 60 | implicit def decodeHConsDerived[K <: Symbol, H, T <: HList](implicit 61 | key: Witness.Aux[K], 62 | decodeH: Lazy[DerivedDecoder[H]], 63 | decodeT: Lazy[DerivedDecoder[T]] 64 | ): DerivedDecoder[FieldType[K, H] :: T] = new DerivedDecoder[FieldType[K, H] :: T] { 65 | def apply(c: HCursor): Decoder.Result[FieldType[K, H] :: T] = for { 66 | h <- c.get(key.value.name)(decodeH.value) 67 | t <- decodeT.value(c) 68 | } yield field[K](h) :: t 69 | } 70 | 71 | implicit def decodeCoproductDerived[K <: Symbol, L, R <: Coproduct](implicit 72 | key: Witness.Aux[K], 73 | decodeL: Lazy[DerivedDecoder[L]], 74 | decodeR: Lazy[DerivedDecoder[R]] 75 | ): DerivedDecoder[FieldType[K, L] :+: R] = new DerivedDecoder[FieldType[K, L] :+: R] { 76 | def apply(c: HCursor): Decoder.Result[FieldType[K, L] :+: R] = 77 | c.downField(key.value.name).focus match { 78 | case Some(value) => value.as(decodeL.value).map(l => Inl(field(l))) 79 | case None => decodeR.value(c).map(Inr(_)) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/better/DerivedObjectEncoder.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.better 2 | 3 | import export.exports 4 | import io.circe.{ Encoder, JsonObject, ObjectEncoder } 5 | import shapeless._, shapeless.labelled.FieldType 6 | 7 | abstract class DerivedObjectEncoder[A] extends ObjectEncoder[A] 8 | 9 | @exports 10 | final object DerivedObjectEncoder extends LowPriorityDerivedObjectEncoders { 11 | implicit val encodeHNil: DerivedObjectEncoder[HNil] = new DerivedObjectEncoder[HNil] { 12 | def encodeObject(a: HNil): JsonObject = JsonObject.empty 13 | } 14 | 15 | implicit def encodeHCons[K <: Symbol, H, T <: HList](implicit 16 | key: Witness.Aux[K], 17 | encodeH: Lazy[Encoder[H]], 18 | encodeT: Lazy[DerivedObjectEncoder[T]] 19 | ): DerivedObjectEncoder[FieldType[K, H] :: T] = new DerivedObjectEncoder[FieldType[K, H] :: T] { 20 | def encodeObject(a: FieldType[K, H] :: T): JsonObject = a match { 21 | case h :: t => (key.value.name -> encodeH.value(h)) +: encodeT.value.encodeObject(t) 22 | } 23 | } 24 | 25 | implicit val encodeCNil: DerivedObjectEncoder[CNil] = new DerivedObjectEncoder[CNil] { 26 | def encodeObject(a: CNil): JsonObject = 27 | sys.error("No JSON representation of CNil (this shouldn't happen)") 28 | } 29 | 30 | implicit def encodeCoproduct[K <: Symbol, L, R <: Coproduct](implicit 31 | key: Witness.Aux[K], 32 | encodeL: Lazy[Encoder[L]], 33 | encodeR: Lazy[DerivedObjectEncoder[R]] 34 | ): DerivedObjectEncoder[FieldType[K, L] :+: R] = new DerivedObjectEncoder[FieldType[K, L] :+: R] { 35 | def encodeObject(a: FieldType[K, L] :+: R): JsonObject = a match { 36 | case Inl(l) => JsonObject.singleton(key.value.name, encodeL.value(l)) 37 | case Inr(r) => encodeR.value.encodeObject(r) 38 | } 39 | } 40 | 41 | implicit def encodeCaseClass[A, R <: HList](implicit 42 | gen: LabelledGeneric.Aux[A, R], 43 | encodeR: Lazy[DerivedObjectEncoder[R]] 44 | ): DerivedObjectEncoder[A] = new DerivedObjectEncoder[A] { 45 | def encodeObject(a: A): JsonObject = encodeR.value.encodeObject(gen.to(a)) 46 | } 47 | 48 | implicit def encodeAdt[A, R <: Coproduct](implicit 49 | gen: LabelledGeneric.Aux[A, R], 50 | encodeR: Lazy[DerivedObjectEncoder[R]] 51 | ): DerivedObjectEncoder[A] = new DerivedObjectEncoder[A] { 52 | def encodeObject(a: A): JsonObject = encodeR.value.encodeObject(gen.to(a)) 53 | } 54 | } 55 | 56 | private[circe] trait LowPriorityDerivedObjectEncoders { 57 | implicit def encodeHConsDerived[K <: Symbol, H, T <: HList](implicit 58 | key: Witness.Aux[K], 59 | encodeH: Lazy[DerivedObjectEncoder[H]], 60 | encodeT: Lazy[DerivedObjectEncoder[T]] 61 | ): DerivedObjectEncoder[FieldType[K, H] :: T] = new DerivedObjectEncoder[FieldType[K, H] :: T] { 62 | def encodeObject(a: FieldType[K, H] :: T): JsonObject = a match { 63 | case h :: t => (key.value.name -> encodeH.value(h)) +: encodeT.value.encodeObject(t) 64 | } 65 | } 66 | 67 | implicit def encodeCoproductDerived[K <: Symbol, L, R <: Coproduct](implicit 68 | key: Witness.Aux[K], 69 | encodeL: Lazy[DerivedObjectEncoder[L]], 70 | encodeR: Lazy[DerivedObjectEncoder[R]] 71 | ): DerivedObjectEncoder[FieldType[K, L] :+: R] = new DerivedObjectEncoder[FieldType[K, L] :+: R] { 72 | def encodeObject(a: FieldType[K, L] :+: R): JsonObject = a match { 73 | case Inl(l) => JsonObject.singleton(key.value.name, encodeL.value(l)) 74 | case Inr(r) => encodeR.value.encodeObject(r) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/better/ExportHookAdapter.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.better 2 | 3 | import io.circe.export.Exported 4 | import export.Export5 5 | 6 | /** 7 | * This is necessary only because circe has its own simplified 8 | * [[io.circe.export.Exported]] representation. 9 | */ 10 | trait ExportHookAdapter { 11 | implicit def convertExport5[A](implicit e5: Export5[A]): Exported[A] = Exported(e5.instance) 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/better/auto.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.better 2 | 3 | import export.reexports 4 | 5 | @reexports[DerivedDecoder, DerivedObjectEncoder] 6 | object auto extends ExportHookAdapter 7 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/dryer/decoders.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.dryer 2 | 3 | import cats.data.Xor 4 | import io.circe.{ Decoder, DecodingFailure } 5 | import shapeless.{ :+:, ::, CNil, Coproduct, HList, Inl, Inr, HNil } 6 | import shapeless.{ LabelledTypeClass, LabelledTypeClassCompanion } 7 | 8 | object decoders extends LabelledTypeClassCompanion[Decoder] { 9 | val typeClass: LabelledTypeClass[Decoder] = new LabelledTypeClass[Decoder] { 10 | def emptyProduct: Decoder[HNil] = Decoder.const(HNil) 11 | 12 | def product[H, T <: HList](name: String, 13 | decodeH: Decoder[H], 14 | decodeT: Decoder[T] 15 | ): Decoder[H :: T] = Decoder.instance { c => 16 | for { 17 | h <- c.get(name)(decodeH) 18 | t <- decodeT(c) 19 | } yield h :: t 20 | } 21 | 22 | def emptyCoproduct: Decoder[CNil] = 23 | Decoder.instance(c => Xor.left(DecodingFailure("CNil", c.history))) 24 | 25 | def coproduct[L, R <: Coproduct]( 26 | name: String, 27 | decodeL: => Decoder[L], 28 | decodeR: => Decoder[R] 29 | ): Decoder[L :+: R] = Decoder.instance { c => 30 | c.downField(name).focus match { 31 | case Some(value) => decodeL.decodeJson(value).map(Inl(_)) 32 | case None => decodeR(c).map(Inr(_)) 33 | } 34 | } 35 | 36 | def project[F, G](instance: => Decoder[G], to: F => G, from: G => F): Decoder[F] = 37 | instance.map(from) 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/dryer/encoders.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.dryer 2 | 3 | import io.circe.{ Encoder, Json, JsonObject } 4 | import shapeless.{ :+:, ::, CNil, Coproduct, HList, Inl, Inr, HNil } 5 | import shapeless.{ LabelledTypeClass, LabelledTypeClassCompanion } 6 | 7 | object encoders extends LabelledTypeClassCompanion[Encoder] { 8 | val typeClass: LabelledTypeClass[Encoder] = new LabelledTypeClass[Encoder] { 9 | def emptyProduct: Encoder[HNil] = Encoder.instance(_ => Json.fromJsonObject(JsonObject.empty)) 10 | 11 | def product[H, T <: HList]( 12 | name: String, 13 | encodeH: Encoder[H], 14 | encodeT: Encoder[T] 15 | ): Encoder[H :: T] = Encoder.instance { 16 | case h :: t => 17 | val encodedTail = encodeT(t).asObject.getOrElse( 18 | sys.error("An HList encoder does not return a JSON object") 19 | ) 20 | 21 | Json.fromJsonObject((name -> encodeH(h)) +: encodedTail) 22 | } 23 | 24 | def emptyCoproduct: Encoder[CNil] = Encoder.instance(_ => 25 | sys.error("No JSON representation of CNil (this shouldn't happen)") 26 | ) 27 | 28 | def coproduct[L, R <: Coproduct]( 29 | name: String, 30 | encodeL: => Encoder[L], 31 | encodeR: => Encoder[R] 32 | ): Encoder[L :+: R] = Encoder.instance { 33 | case Inl(l) => Json.obj(name -> encodeL(l)) 34 | case Inr(r) => encodeR(r) 35 | } 36 | 37 | def project[F, G](instance: => Encoder[G], to: F => G, from: G => F): Encoder[F] = 38 | instance.contramap(to) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/raw/DerivationMacros.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.raw 2 | 3 | import io.circe.{ Decoder, Encoder } 4 | import macrocompat.bundle 5 | import scala.reflect.macros.whitebox 6 | 7 | @bundle 8 | class DerivationMacros(val c: whitebox.Context) { 9 | import c.universe._ 10 | 11 | def materializeDecoderImpl[T: c.WeakTypeTag]: c.Expr[Decoder[T]] = { 12 | val tpe = weakTypeOf[T] 13 | 14 | val primaryConstructor = tpe.decls.collectFirst { 15 | case m: MethodSymbol if m.isPrimaryConstructor => m 16 | } 17 | 18 | primaryConstructor match { 19 | case Some(constructor) => 20 | val fieldNames: List[Name] = constructor.paramLists.flatten.map(_.name) 21 | val decodedNames: List[String] = fieldNames.map(_.decodedName.toString) 22 | val fieldTypes: List[Type] = constructor.paramLists.flatten.map { field => 23 | tpe.decl(field.name).typeSignature 24 | } 25 | val fieldCount = fieldNames.size 26 | val functionParameters = fieldNames.zip(fieldTypes).map { 27 | case (fieldName, fieldType) => 28 | val termName = TermName(fieldName.toString) 29 | q"$termName: $fieldType" 30 | } 31 | val parameters = fieldNames.map { fieldName => 32 | val termName = TermName(fieldName.toString) 33 | q"$termName" 34 | } 35 | 36 | val methodName = TermName(s"forProduct$fieldCount") 37 | 38 | if (fieldCount > 0) c.Expr[Decoder[T]]( 39 | q""" 40 | _root_.io.circe.Decoder.$methodName[..$fieldTypes, $tpe](..$decodedNames)( 41 | (..$functionParameters) => new $tpe(..$parameters) 42 | ) 43 | """ 44 | ) else c.Expr[Decoder[T]](q"_root_.io.circe.Decoder.const(new $tpe())") 45 | case None => c.abort(c.enclosingPosition, s"Could not identify primary constructor for $tpe") 46 | } 47 | } 48 | 49 | def materializeEncoderImpl[T: c.WeakTypeTag]: c.Expr[Encoder[T]] = { 50 | val tpe = weakTypeOf[T] 51 | 52 | val primaryConstructor = tpe.decls.collectFirst { 53 | case m: MethodSymbol if m.isPrimaryConstructor => m 54 | } 55 | 56 | primaryConstructor match { 57 | case Some(constructor) => 58 | val fieldNames: List[Name] = constructor.paramLists.flatten.map(_.name) 59 | val decodedNames: List[String] = fieldNames.map(_.decodedName.toString) 60 | val fieldTypes: List[Type] = constructor.paramLists.flatten.map { field => 61 | tpe.decl(field.name).typeSignature 62 | } 63 | val fieldCount = fieldNames.size 64 | val invocations = fieldNames.map { fieldName => 65 | val termName = TermName(fieldName.toString) 66 | q"toEncode.$termName" 67 | } 68 | 69 | val methodName = TermName(s"forProduct$fieldCount") 70 | 71 | if (fieldCount > 0) c.Expr[Encoder[T]]( 72 | q""" 73 | _root_.io.circe.Encoder.$methodName[..$fieldTypes, $tpe](..$decodedNames)( 74 | toEncode => (..$invocations) 75 | ) 76 | """ 77 | ) else c.Expr[Encoder[T]]( 78 | q""" 79 | _root_.io.circe.Encoder.instance[$tpe](_ => _root_.io.circe.Json.obj()) 80 | """ 81 | ) 82 | case None => c.abort(c.enclosingPosition, s"Could not identify primary constructor for $tpe") 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/raw/package.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation 2 | 3 | import io.circe.{ Decoder, Encoder } 4 | import scala.language.experimental.macros 5 | 6 | package object raw { 7 | implicit def deriveDecoder[A]: Decoder[A] = macro DerivationMacros.materializeDecoderImpl[A] 8 | implicit def deriveEncoder[A]: Encoder[A] = macro DerivationMacros.materializeEncoderImpl[A] 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/simple/CoproductCodecs.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.simple 2 | 3 | import cats.data.Xor 4 | import io.circe.{ Decoder, DecodingFailure, Encoder, JsonObject, ObjectEncoder } 5 | import shapeless.{ :+:, CNil, Coproduct, Inl, Inr, Witness } 6 | import shapeless.labelled.{ FieldType, field } 7 | 8 | trait CoproductCodecs { 9 | implicit val decodeCNil: Decoder[CNil] = 10 | Decoder.instance(c => Xor.left(DecodingFailure("CNil", c.history))) 11 | 12 | implicit def decodeCoproduct[K <: Symbol, L, R <: Coproduct](implicit 13 | key: Witness.Aux[K], 14 | decodeL: Decoder[L], 15 | decodeR: Decoder[R] 16 | ): Decoder[FieldType[K, L] :+: R] = Decoder.instance { c => 17 | c.downField(key.value.name).focus match { 18 | case Some(value) => value.as[L].map(l => Inl(field(l))) 19 | case None => decodeR(c).map(Inr(_)) 20 | } 21 | } 22 | 23 | implicit val encodeCNil: ObjectEncoder[CNil] = 24 | ObjectEncoder.instance(_ => sys.error("No JSON representation of CNil (this shouldn't happen)")) 25 | 26 | implicit def encodeCoproduct[K <: Symbol, L, R <: Coproduct](implicit 27 | key: Witness.Aux[K], 28 | encodeL: Encoder[L], 29 | encodeR: ObjectEncoder[R] 30 | ): ObjectEncoder[FieldType[K, L] :+: R] = ObjectEncoder.instance { 31 | case Inl(l) => JsonObject.singleton(key.value.name, encodeL(l)) 32 | case Inr(r) => encodeR.encodeObject(r) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/simple/HListCodecs.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation.simple 2 | 3 | import io.circe.{ Decoder, Encoder, JsonObject, ObjectEncoder } 4 | import shapeless.{ ::, HList, HNil, Witness } 5 | import shapeless.labelled.{ FieldType, field } 6 | 7 | trait HListCodecs { 8 | implicit val decodeHNil: Decoder[HNil] = Decoder.const(HNil) 9 | 10 | implicit def decodeHCons[K <: Symbol, H, T <: HList](implicit 11 | key: Witness.Aux[K], 12 | decodeH: Decoder[H], 13 | decodeT: Decoder[T] 14 | ): Decoder[FieldType[K, H] :: T] = Decoder.instance { c => 15 | for { 16 | h <- c.get[H](key.value.name) 17 | t <- decodeT(c) 18 | } yield field[K](h) :: t 19 | } 20 | 21 | implicit val encodeHNil: ObjectEncoder[HNil] = ObjectEncoder.instance(_ => JsonObject.empty) 22 | 23 | implicit def encodeHCons[K <: Symbol, H, T <: HList](implicit 24 | key: Witness.Aux[K], 25 | encodeH: Encoder[H], 26 | encodeT: ObjectEncoder[T] 27 | ): ObjectEncoder[FieldType[K, H] :: T] = ObjectEncoder.instance { 28 | case h :: t => (key.value.name -> encodeH(h)) +: encodeT.encodeObject(t) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/scala/io/circe/examples/derivation/simple/package.scala: -------------------------------------------------------------------------------- 1 | package io.circe.examples.derivation 2 | 3 | import io.circe.{ Decoder, ObjectEncoder } 4 | import shapeless.LabelledGeneric 5 | 6 | package object simple extends HListCodecs with CoproductCodecs { 7 | implicit def decodeGeneric[A, R](implicit 8 | gen: LabelledGeneric.Aux[A, R], 9 | decodeR: Decoder[R] 10 | ): Decoder[A] = decodeR.map(gen.from) 11 | 12 | implicit def encodeGeneric[A, R](implicit 13 | gen: LabelledGeneric.Aux[A, R], 14 | encodeR: ObjectEncoder[R] 15 | ): ObjectEncoder[A] = ObjectEncoder.instance(a => encodeR.encodeObject(gen.to(a))) 16 | } 17 | -------------------------------------------------------------------------------- /project/Boilerplate.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Boilerplate { 4 | def ccs(size: Int)(dir: File): Seq[File] = { 5 | val file = dir / "io" / "circe" / "examples" / "derivation" / "definitions" / "ccs.scala" 6 | 7 | val header = """ 8 | |package io.circe.examples.derivation.definitions 9 | | 10 | |case class LongWrapper(value: Long) 11 | |case class ShortWrapper(value: Short) 12 | | 13 | """.stripMargin 14 | 15 | val content = (0 to size).map { i => 16 | val members = (1 to i).map { j => 17 | val tpe = (j % 5) match { 18 | case 0 => "String" 19 | case 1 => "Int" 20 | case 2 => "Char" 21 | case 3 => "ShortWrapper" 22 | case _ => "LongWrapper" 23 | } 24 | 25 | f"m$j%02d: $tpe" 26 | }.mkString(", ") 27 | 28 | f"case class Cc$i%02d($members)" 29 | }.mkString("\n") 30 | 31 | IO.write(file, header ++ content) 32 | 33 | List(file) 34 | } 35 | 36 | def adts(size: Int)(dir: File): Seq[File] = { 37 | val file = dir / "io" / "circe" / "examples" / "derivation" / "definitions" / "adts.scala" 38 | 39 | val header = """ 40 | |package io.circe.examples.derivation.definitions 41 | | 42 | |""".stripMargin 43 | 44 | val content = (1 to size).map { i => 45 | val base = f"sealed trait Base$i%02d" 46 | val members = (1 to i).map { j => 47 | 48 | f"case class Bcc$i%02d$j%02d(s: String) extends Base$i%02d" 49 | }.mkString("\n") 50 | 51 | f"$base\n$members" 52 | }.mkString("\n\n") 53 | 54 | IO.write(file, header ++ content) 55 | 56 | List(file) 57 | } 58 | 59 | def deeps(size: Int)(dir: File): Seq[File] = { 60 | val file = dir / "io" / "circe" / "examples" / "derivation" / "definitions" / "deeps.scala" 61 | 62 | val header = """ 63 | |package io.circe.examples.derivation.definitions 64 | | 65 | |case class Deep00(s: String) 66 | |""".stripMargin 67 | 68 | val content = (1 to size).map { i => 69 | f"case class Deep$i%02d(d: Deep${ i - 1 }%02d)" 70 | }.mkString("\n") 71 | 72 | IO.write(file, header ++ content) 73 | 74 | List(file) 75 | } 76 | 77 | def benches(size: Int, reps: Int)(dir: File): Seq[File] = { 78 | val raw = dir / "io" / "circe" / "examples" / "derivation" / "bench" / "Raw.scala" 79 | val simple = dir / "io" / "circe" / "examples" / "derivation" / "bench" / "Simple.scala" 80 | //val dryer = dir / "io" / "circe" / "examples" / "derivation" / "bench" / "Dryer.scala" 81 | val better = dir / "io" / "circe" / "examples" / "derivation" / "bench" / "Better.scala" 82 | val generic = dir / "io" / "circe" / "examples" / "derivation" / "bench" / "Generic.scala" 83 | 84 | def header(name: String, short: String, deriverImport: String) = s""" 85 | |package io.circe.examples.derivation.bench 86 | | 87 | |import io.circe.examples.derivation.definitions._ 88 | |import scala.concurrent.duration.Duration 89 | |import shapeless.test.compileTime 90 | |import $deriverImport 91 | | 92 | |object $name extends Bench("$short") { 93 | | val size = $size 94 | |""".stripMargin 95 | 96 | def ccs(raw: Boolean) = (if (raw) (0 to math.min(size, 22)) else (0 to size)).map { i => 97 | f""" compileTime("io.circe.Decoder[Cc$i%02d]")""" 98 | }.mkString(",\n") 99 | 100 | val adts = (1 to size).map { i => 101 | f""" compileTime("io.circe.Decoder[Base$i%02d]")""" 102 | }.mkString(",\n") 103 | 104 | val deeps = (0 to size).map { i => 105 | f""" compileTime("io.circe.Decoder[Deep$i%02d]")""" 106 | }.mkString(",\n") 107 | 108 | def ccContent(raw: Boolean) = s""" 109 | | val ccResults: List[Duration] = List( 110 | |${ List.fill(reps)(ccs(raw)).mkString(",\n") } 111 | | ) 112 | |""".stripMargin 113 | 114 | def adtContent(raw: Boolean) = if (!raw) s""" 115 | | val adtResults: List[Duration] = List( 116 | |${ List.fill(reps)(adts).mkString(",\n") } 117 | | ) 118 | |""".stripMargin else " val adtResults: List[Duration] = Nil" 119 | 120 | val deepContent = s""" 121 | | val deepResults: List[Duration] = List( 122 | |${ List.fill(reps)(deeps).mkString(",\n") } 123 | | ) 124 | |""".stripMargin 125 | 126 | def content(raw: Boolean) = ccContent(raw) + adtContent(raw) + deepContent + "\n}" 127 | 128 | val rawImport = "io.circe.examples.derivation.raw._" 129 | val simpleImport = "io.circe.examples.derivation.simple._" 130 | val dryerImport = "io.circe.examples.derivation.dryer.decoders._" 131 | val betterImport = "io.circe.examples.derivation.better.auto._" 132 | val genericImport = "io.circe.generic.auto._" 133 | 134 | IO.write(raw, header("Raw", "raw macros", rawImport) + content(true)) 135 | IO.write(simple, header("Simple", "simple Shapeless", simpleImport) + content(false)) 136 | //IO.write(dryer, header("Dryer", "D", dryerImport) + content(false)) 137 | IO.write(better, header("Better", "better Shapeless", betterImport) + content(false)) 138 | IO.write(generic, header("Generic", "circe-generic", genericImport) + content(false)) 139 | 140 | List(raw, simple, better, generic) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.12 2 | --------------------------------------------------------------------------------