Last active
January 5, 2021 22:36
-
-
Save neko-kai/d4d09c129dc85e67baf8d25f651c2c83 to your computer and use it in GitHub Desktop.
Traits for abstracting Circe derivation boilerplate (with type discriminator field)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import io.circe._ | |
import io.circe.generic.decoding.DerivedDecoder | |
import io.circe.generic.encoding.DerivedObjectEncoder | |
import io.circe.syntax._ | |
import shapeless._ | |
import io.circe.shapes._ | |
// workaround for scalac bug: | |
// super constructor cannot be passed a self reference unless parameter is declared by-name | |
//object Abc extends DefaultCodecs(implicitly[Lazy[CodecWithDiscriminator[Abc]]].value) | |
implicit object AbcCodecs extends CodecWithDiscriminator[Abc] { | |
val field = "kind" | |
val kind = "type:abc" | |
} | |
implicit object CbaCodecs extends CodecWithDiscriminator[Cba] { | |
val field = "kind" | |
val kind = "type:cba" | |
} | |
sealed trait X | |
case class Abc(x: Int) extends X | |
case class Cba(x: String) extends X | |
// order matters, Abc has to be declared after AbcCodecs, or it doesn't resolve | |
object Abc extends ImplicitCodecs[Abc] | |
object Cba extends ImplicitCodecs[Cba] | |
object X extends SimpleGenericCodecs[X] | |
//////////////////////////////// | |
val jsonAbc = Abc(5).asJson | |
val upcast: X = Cba("Hello") | |
val jsonUpcast = upcast.asJson | |
val list: List[X] = List(Abc(3), Cba("sss")) | |
val jsonList = list.asJson | |
assert(jsonAbc.as[Abc] contains Abc(5)) | |
assert(jsonUpcast.as[X] contains upcast) | |
assert(jsonList.as[List[X]] contains list) | |
//////////////////////////////// | |
// workaround for scalac bug: | |
// super constructor cannot be passed a self reference unless parameter is declared by-name | |
// happens when in constructor trying to request instances of Generic or DerivedDecoder, DerivedEncoder | |
// and then trying to instantiate in companion object with the type of companion class | |
// | |
// abstract class GiveCodecs[T: DerivedDecoder: DerivedEncoder] | |
// case class X() | |
// object X extends GiveCodecs[X] | |
// // works: object XCodecs extends GiveCodecs[X] | |
trait CodecPack[T] { | |
def decoder: Decoder[T] | |
def encoder: Encoder[T] | |
} | |
abstract class ImplicitCodecs[T](implicit delegate: CodecPack[T]) extends CodecPack[T] { | |
implicit val decoder: Decoder[T] = delegate.decoder | |
implicit val encoder: Encoder[T] = delegate.encoder | |
} | |
abstract class CodecWithDiscriminator[T](implicit codecs: GetDerivedCodecs[T]) extends CodecPack[T] { | |
import codecs._ | |
def field: String | |
def kind: String | |
implicit val decoder: Decoder[T] = dec.validate( | |
_.get[String](field).exists(_ == kind) | |
, s"No field `$field`: `$kind` found in JSON object, JSON format requires a type marker `$field`" | |
) | |
implicit val encoder: ObjectEncoder[T] = enc.mapJsonObject { | |
_.add("kind", Json.fromString(kind)) | |
} | |
} | |
abstract class SimpleGenericCodecs[T](implicit codecs: GetGenericCodecs[T]) extends CodecPack[T] { | |
import codecs._ | |
implicit val decoder: Decoder[T] = dec.map(gen.from) | |
implicit val encoder: Encoder[T] = enc.contramap(gen.to) | |
} | |
//////////////////////////////// | |
// Workaround for inability to curry type parameters - we need to fetch decoders for Repr, without specifying Repr | |
trait GetGenericCodecs[T] { | |
type Repr | |
val gen: Generic.Aux[T, Repr] | |
val dec: Decoder[Repr] | |
val enc: Encoder[Repr] | |
} | |
implicit def getGenericCodecs[T, R](implicit generic: Generic.Aux[T, R], decoder: Decoder[R], encoder: Encoder[R]): GetGenericCodecs[T] = | |
new GetGenericCodecs[T] { | |
override type Repr = R | |
override val gen: Generic.Aux[T, R] = generic | |
override val dec: Decoder[R] = decoder | |
override val enc: Encoder[R] = encoder | |
} | |
// workaround illegal cyclic reference when trying to request Derived* implicitly | |
/// AND | |
// workaround infinite loop NullPtr Exception on trying get Decoders (implicit is in scope of itself and calls itself): | |
// implicit val dec: Decoder[gen.Repr] = implicitly[Decoder[gen.Repr]] | |
trait GetDerivedCodecs[T] { | |
val dec: DerivedDecoder[T] | |
val enc: DerivedObjectEncoder[T] | |
} | |
implicit def getDerivedCodecs[T: DerivedDecoder: DerivedObjectEncoder]: GetDerivedCodecs[T] = | |
new GetDerivedCodecs[T] { | |
override val dec: DerivedDecoder[T] = implicitly | |
override val enc: DerivedObjectEncoder[T] = implicitly | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import io.circe._ | |
import io.circe.generic.decoding.DerivedDecoder | |
import io.circe.generic.encoding.DerivedObjectEncoder | |
import io.circe.syntax._ | |
//import io.circe.generic.semiauto._ | |
import shapeless._ | |
sealed trait X | |
case class Abc(x: Int) extends X | |
case class Cba(x: String) extends X | |
// workaround for scalac bug: | |
// super constructor cannot be passed a self reference unless parameter is declared by-name | |
object Abc extends DefaultCodecs(AbcCodecs) | |
object AbcCodecs extends CodecWithDiscriminator[Abc] { | |
val field = "kind" | |
val kind = "type:abc" | |
} | |
object Cba extends DefaultCodecs(CbaCodecs) | |
object CbaCodecs extends CodecWithDiscriminator[Cba] { | |
val field = "kind" | |
val kind = "type:cba" | |
} | |
object X extends DefaultCodecs(XCodecs) | |
object XCodecs extends NaiveSealedCodec[X]()(Generic[X]) | |
println(XCodecs.ev) | |
val jsonAbc = Abc(5).asJson | |
assert(jsonAbc.as[Abc] contains Abc(5)) | |
val upcast: X = Cba("Hello") | |
val jsonUpcast = upcast.asJson | |
assert(jsonUpcast.as[X] contains upcast) | |
val list: List[X] = List(Abc(3), Cba("sss")) | |
val json = list.asJson | |
assert(json.as[List[X]] contains list) | |
//////////////////////////////// | |
trait CodecPack[T] { | |
def decoder: Decoder[T] | |
def encoder: Encoder[T] | |
} | |
abstract class DefaultCodecs[T](delegate: CodecPack[T]) extends CodecPack[T] { | |
implicit val decoder: Decoder[T] = delegate.decoder | |
implicit val encoder: Encoder[T] = delegate.encoder | |
} | |
abstract class CodecWithDiscriminator[T: DerivedDecoder: DerivedObjectEncoder] extends CodecPack[T] { | |
import io.circe.generic.semiauto._ | |
def field: String | |
def kind: String | |
implicit val decoder: Decoder[T] = deriveDecoder[T].validate( | |
_.get[String](field).exists(_ == kind) | |
, s"No field `$field`: `$kind` found in JSON object, JSON format requires a type marker `$field`" | |
) | |
implicit val encoder: ObjectEncoder[T] = deriveEncoder[T].mapJsonObject { | |
_.add("kind", Json.fromString(kind)) | |
} | |
} | |
abstract class NaiveSealedCodec[T](implicit val gen: Generic[T]) extends CodecPack[T] { | |
type Z | |
implicit val ev: gen.Repr <:< Boolean = implicitly | |
implicit private val dec: Decoder[gen.Repr] = implicitly[Decoder[gen.Repr]] | |
implicit private val enc: Encoder[gen.Repr] = implicitly[Encoder[gen.Repr]] | |
implicit val decoder: Decoder[T] = dec.map(gen.from) | |
implicit val encoder: Encoder[T] = enc.contramap(gen.to) | |
} | |
//abstract class NaiveSealedCodec[T: Generic] extends CodecPack[T] { | |
//// implicit val decoder: Decoder[T] = deriveDecoder | |
//// implicit val encoder: ObjectEncoder[T] = deriveEncoder | |
// implicit def decoder(implicit dec: Lazy[Decoder[Generic[T]]]): Decoder[T] = dec.value.map(Generic[T].from) | |
// implicit def encoder(implicit enc: Lazy[Encoder[Generic[T]]]): Encoder[T] = enc.value.contramap(Generic[T].to) | |
//} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import io.circe._ | |
import io.circe.generic.decoding.DerivedDecoder | |
import io.circe.generic.encoding.DerivedObjectEncoder | |
import io.circe.syntax._ | |
//import io.circe.generic.semiauto._ | |
import shapeless._ | |
import io.circe.shapes._ | |
sealed trait X | |
case class Abc(x: Int) extends X | |
case class Cba(x: String) extends X | |
object Abc extends CodecWithDiscriminator[Abc]{ | |
val field = "kind" | |
val kind = "type:abc" | |
// shapeless Lazy is fucking anal magic | |
// ^ Can use without semiauto in scope because Lazy[] is only looked up once | |
} | |
object Cba extends CodecWithDiscriminator[Cba]{ | |
val field = "kind" | |
val kind = "type:cba" | |
} | |
object X extends NaiveSealedCodec[X] { | |
import io.circe.shapes._ | |
type Z = the.`Generic[X]`.Repr | |
override final val lgen = Lazy.mkLazy[Generic[X]] | |
/// ^ err doesn't work for shapes for some reason | |
} | |
//object X extends Z { | |
// import io.circe.shapes._ | |
// | |
// implicit val decoder: Decoder[X] = genericDecoder(Generic[X]) | |
// implicit val encoder: Encoder[X] = genericEncoder(Generic[X]) | |
//} | |
// | |
//trait Z { | |
// def genericDecoder[T, Repr](gen: Generic.Aux[T, Repr])(implicit dec: Decoder[gen.Repr]): Decoder[T] = dec.map(gen.from) | |
// def genericEncoder[T, Repr](gen: Generic.Aux[T, Repr])(implicit enc: Encoder[gen.Repr]): Encoder[T] = enc.contramap(gen.to) | |
//} | |
val jsonAbc = Abc(5).asJson | |
assert(jsonAbc.as[Abc] contains Abc(5)) | |
val upcast: X = Cba("Hello") | |
val jsonUpcast = upcast.asJson | |
assert(jsonUpcast.as[X] contains upcast) | |
val list: List[X] = List(Abc(3), Cba("sss")) | |
val json = list.asJson | |
assert(json.as[List[X]] contains list) | |
//////////////////////////////// | |
abstract class CodecWithDiscriminator[T] { | |
import io.circe.generic.semiauto._ | |
def field: String | |
def kind: String | |
implicit def decoder(implicit dec: Lazy[DerivedDecoder[T]]): Decoder[T] = deriveDecoder[T].validate( | |
_.get[String](field).exists(_ == kind) | |
, s"No field `$field`: `$kind` found in JSON object, JSON format requires a type marker `$field`" | |
) | |
implicit def encoder(implicit enc: Lazy[DerivedObjectEncoder[T]]): ObjectEncoder[T] = deriveEncoder[T].mapJsonObject { | |
_.add("kind", Json.fromString(kind)) | |
} | |
} | |
abstract class NaiveSealedCodec[T] { | |
import io.circe.shapes._ | |
type Z <: Coproduct | |
val lgen: Lazy[Generic.Aux[T, Z]] | |
implicit def decoder(implicit dec: Lazy[Decoder[Z]]): Decoder[T] = dec.value.map(lgen.value.from) | |
implicit def encoder(implicit enc: Lazy[Encoder[Z]]): Encoder[T] = enc.value.contramap(lgen.value.to) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment