Skip to content

Instantly share code, notes, and snippets.

@neko-kai
Last active January 5, 2021 22:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neko-kai/d4d09c129dc85e67baf8d25f651c2c83 to your computer and use it in GitHub Desktop.
Save neko-kai/d4d09c129dc85e67baf8d25f651c2c83 to your computer and use it in GitHub Desktop.
Traits for abstracting Circe derivation boilerplate (with type discriminator field)
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
}
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)
//}
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