Skip to content

Commit

Permalink
Support Circe's KeyDecoder & KeyEncoder
Browse files Browse the repository at this point in the history
  • Loading branch information
gvolpe authored and catostrophe committed Apr 28, 2021
1 parent 87776fa commit 28f85b4
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 4 deletions.
37 changes: 34 additions & 3 deletions circeMagnolia/src/main/scala/derevo/circe/magnolia/circe.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package derevo.circe.magnolia

import derevo.{Derevo, Derivation, PolyDerivation, delegating}
import derevo.{Derevo, Derivation, PolyDerivation, delegating, NewTypeDerivation}
import io.circe.magnolia.configured.Configuration
import io.circe.{Decoder, Encoder}
import derevo.NewTypeDerivation
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import magnolia.{CaseClass, Magnolia, SealedTrait}

@delegating("io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder")
object decoder extends Derivation[Decoder] with NewTypeDerivation[Decoder] {
Expand All @@ -24,3 +24,34 @@ object customizableDecoder extends Derivation[Decoder] {
object customizableEncoder extends Derivation[Encoder] {
def instance[A]: Encoder[A] = macro Derevo.delegate[Encoder, A]
}

object keyDecoder extends Derivation[KeyDecoder] with NewTypeDerivation[KeyDecoder] {
type Typeclass[T] = KeyDecoder[T]

def combine[T](ctx: CaseClass[KeyDecoder, T]): KeyDecoder[T] = new KeyDecoder[T] {
def apply(key: String): Option[T] = {
val parts = key.split("::")
if (parts.length != ctx.parameters.length) None
else ctx.constructMonadic(p => p.typeclass.apply(parts(p.index)))
}
}

def instance[T]: KeyDecoder[T] = macro Magnolia.gen[T]
}

private[circe] class keyEncoder(sep: String = "::") {
type Typeclass[T] = KeyEncoder[T]

def combine[T](ctx: CaseClass[KeyEncoder, T]): KeyEncoder[T] =
if (ctx.isObject) _ => ctx.typeName.short
else { cc =>
ctx.parameters.view.map(p => p.typeclass(p.dereference(cc))).mkString(sep)
}

def dispatch[T](ctx: SealedTrait[KeyEncoder, T]): KeyEncoder[T] =
obj => ctx.dispatch(obj)(sub => sub.typeclass(sub.cast(obj)))

def instance[T]: KeyEncoder[T] = macro Magnolia.gen[T]
}

object keyEncoder extends keyEncoder("::") with Derivation[KeyEncoder] with NewTypeDerivation[KeyEncoder]
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.circe._
import io.circe.magnolia.configured.Configuration
import io.circe.syntax._
import io.circe.parser._
//import io.circe.derivation.renaming
import io.estatico.newtype.macros.newtype

import org.scalatest.flatspec.AnyFlatSpec

Expand Down Expand Up @@ -72,6 +72,58 @@ class CirceDerivationSpec extends AnyFlatSpec {
assert(decode[SealedTrait](barJson) == Right(bar))
assert(decode[SealedTrait](bazJson) == Right(baz))
}

it should "derive key codecs for Map data-structures" in {
import KeyCodecs._

val kv1 = KVStore(Map(StoreKey(1) -> StoreValue("uno"), StoreKey(2) -> StoreValue("dos")))
val kv1Json =
"""
|{
| "1" : "uno",
| "2" : "dos"
|}
|""".stripMargin.filterNot(_.isWhitespace)

assert(Encoder[KVStore].apply(kv1).noSpaces == kv1Json)
assert(decode[KVStore](kv1Json) == Right(kv1))

val kv2 = SimpleKV(Map(SimpleKey("jeden") -> SimpleValue(List(1.0)), SimpleKey("dwa") -> SimpleValue(List(2.0))))
val kv2Json =
"""
|{
| "kvs" : {
| "jeden" : {
| "value" : [
| 1.0
| ]
| },
| "dwa" : {
| "value" : [
| 2.0
| ]
| }
| }
|}
|""".stripMargin.filterNot(_.isWhitespace)

assert(Encoder[SimpleKV].apply(kv2).noSpaces == kv2Json)
assert(decode[SimpleKV](kv2Json) == Right(kv2))

val kv3 = DeadSimpleKV(Map(0 -> "zero", 1 -> "one"))
val kv3Json =
"""
|{
| "kvs" : {
| "0" : "zero",
| "1" : "one"
| }
|}
|""".stripMargin.filterNot(_.isWhitespace)

assert(Encoder[DeadSimpleKV].apply(kv3).noSpaces == kv3Json)
assert(decode[DeadSimpleKV](kv3Json) == Right(kv3))
}
}

@derive(customizableEncoder, customizableDecoder)
Expand All @@ -86,3 +138,30 @@ object SealedTrait {
@derive(encoder, decoder)
case class Baz(baz: String) extends SealedTrait
}

object KeyCodecs {
@derive(decoder, encoder, keyDecoder, keyEncoder)
@newtype
case class StoreKey(value: Int)

@derive(decoder, encoder)
@newtype
case class StoreValue(value: String)

@derive(decoder, encoder)
@newtype
case class KVStore(kvs: Map[StoreKey, StoreValue])

// no newtypes here
@derive(decoder, encoder, keyDecoder, keyEncoder)
case class SimpleKey(value: String)

@derive(decoder, encoder)
case class SimpleValue(value: List[Double])

@derive(decoder, encoder)
case class SimpleKV(kvs: Map[SimpleKey, SimpleValue])

@derive(decoder, encoder)
case class DeadSimpleKV(kvs: Map[Int, String])
}

0 comments on commit 28f85b4

Please sign in to comment.