Codec for coproducts with discriminator - scala

We're trying to have circe codecs for shapeless coproducts whith a class name as the discriminator :
object CoproductCodecWrong extends App {
import io.circe.Codec
import shapeless.{ :+:, CNil, Coproduct }
import io.circe.shapes._
import io.circe.literal._
case class A()
case class B(a: String)
type AB = A :+: B :+: CNil
case class H(ab: AB)
implicit val encodeA: Codec[A] = io.circe.generic.semiauto.deriveCodec
implicit val encodeB: Codec[B] = io.circe.generic.semiauto.deriveCodec
implicit val hCodec: Codec.AsObject[H] = io.circe.generic.semiauto.deriveCodec[H]
val hA: H = H(Coproduct[AB](A()))
val hB: H = H(Coproduct[AB](B("q")))
}
It doesn't work because by default the encoder doesn't produce any information that allows to distinguish both cases:
println("hA:\n" + hCodec.apply(hA).toString())
println("hB:\n" + hCodec.apply(hB).toString())
...
hA:
{
"ab" : {
}
}
hB:
{
"ab" : {
"a" : "q"
}
}
Decoding:
println(hCodec.decodeJson(json"""{"ab" : {}}"""))
println(hCodec.decodeJson(json"""{"ab" : {"a" : "q"}}"""))
...
Right(H(Inl(A())))
Right(H(Inl(A())))
It's always A that is found, which is very dangerous as it's working seemingly correctly but should be failing.
We've found a solution using Unions:
import io.circe.{ CursorOp, Decoder, DecodingFailure, Encoder }
import shapeless.ops.coproduct.Folder
import shapeless.ops.union.Values
import shapeless.union.Union
import shapeless.Poly1
type ABL = Union.`'A->A,'B->B`.T
object ABPoly extends Poly1 {
implicit val atA: Case.Aux[A, ABL] = at[A] { z =>
Union[ABL](A = z)
}
implicit val atB: Case.Aux[B, ABL] = at[B] { z =>
Union[ABL](B = z)
}
}
def coproductToLabeledCoproduct[Co <: Coproduct, Labeled <: Coproduct](t: Poly1)(
implicit E: Encoder[Labeled],
D: Decoder[Labeled],
V: Values.Aux[Labeled, Co],
F: Folder.Aux[t.type, Co, Labeled]
): Codec[Co] = {
import io.circe.syntax._
Codec.from(
Decoder.instance { input =>
input.focus
.map(_.as[Labeled].map(x => shapeless.union.unionOps(x).values: Co))
.getOrElse(Left(DecodingFailure("Should be available", { List(CursorOp.DownField("coproduct instance")) })))
},
Encoder.instance(a => a.fold(t).asJson)
)
}
implicit val abToABL: Codec[AB] = coproductToLabeledCoproduct[AB, ABL](ABPoly)
implicit val hCodec: Codec.AsObject[H] = io.circe.generic.semiauto.deriveCodec[H]
The result of encoding/decoding is correct now:
hA:
{
"ab" : {
"A" : {
}
}
}
hB:
{
"ab" : {
"B" : {
"a" : "q"
}
}
}
Right(H(Inl(A())))
Right(H(Inr(Inl(B(q)))))
But it's a bit complex, and hard to maintain for multiple classes.
Is there a better way?
EDIT:
circe-generic-extras doesn't work as it doesn't have a support for coproducts, only for sealed traits and enums. This gives the same result without a discriminator:
implicit val configuration: Configuration = Configuration.default.withDiscriminator("tag")
implicit val encodeA: Codec[A] = io.circe.generic.extras.semiauto.deriveConfiguredCodec
implicit val encodeB: Codec[B] = io.circe.generic.extras.semiauto.deriveConfiguredCodec
implicit val hCodec: Codec.AsObject[H] = io.circe.generic.extras.semiauto.deriveConfiguredCodec[H]

If you are using HLists and Coproducts directly then I guess something is broken in the code that requires that but I would probably use something like:
import shapeless._
import io.circe._
import io.circe.syntax._
class Discriminator(val fieldName: String, val classNameMapper: String => String)
implicit def cnilCodec: Codec[CNil] =
Codec.from(Decoder.instance[CNil](_ => Left(DecodingFailure("should never happen", Nil))), Encoder.instance(_ => ???))
implicit def coproductCodec[LeftLabel, LeftType: Codec, Right <: Coproduct: Codec](
implicit label: Witness.Aux[LeftLabel],
discriminator: Discriminator
): Codec[FieldType[LeftLabel, LeftType] :+: Right] = {
val thisLabel = discriminator.classNameMapper(label.value.toString)
val decoder = Decoder.instance[FieldType[LeftLabel, LeftType] :+: Right] { hcursor =>
hcursor
.get[String](discriminator.fieldName)
.leftMap(_.copy(message = s"Expected discriminator key ${discriminator.fieldName}"))
.flatMap { thisLabel =>
if (thisLabel == thisLabel) Decoder[LeftType].apply(hcursor).map(_.asInstanceOf[FieldType[LeftLabel, LeftType]]).map(Inl(_))
else Decoder[Right].apply(hcursor).map(Inr(_))
}
}
val encoder = Encoder.instance[FieldType[LeftLabel, LeftType] :+: Right] {
case Inl(value) => (value: LeftType).asJson.deepMerge(Json.fromFields(Seq(discriminator.fieldName -> thisLabel.asJson)))
case Inr(tail) => tail.asJson
}
Codec.from(decoder, encoder)
}
Whether it's simpler or more complicated it's a matter of taste.
For me personally this would be a red flag in deciding whether or not use the library in my code. Especially considering that you are forced to use shapeless directly with Circe, which supports sealed hierarchies out of the box.
I would use Circe Generic Extras with a sealed hierarchy.
It allows defining discriminator field name and format, providing support for auto- and semiauto derivation as well as macro annotations (#ConfiguredJsonCodec instead of #JsonCodec).
And then I would just translate this into this sad internal representation with:
def rewriteForCoproduct[A: Codec, ARepr <: Coproduct](implicit gen: LabelledGeneric.Aux[A, ARepr]): Codec[ARepr] =
Codec.from(Codec[A].map[ARepr](gen.to), Codec[A].contramap[ARepr](gen.from))

Related

Type Class Derivation accessing default values

Is there a clean way to access the default values of a case class fields when performing type class derivation in Scala 3 using Mirrors? For example:
case class Foo(s: String = "bar", i: Int, d: Double = Math.PI)
Mirror.Product.MirroredElemLabels will be set to ("s", "i", "d"). Is there anything like: (Some["bar"], None, Some[3.141592653589793])?
If not could this be achieved using Macros? Can I use the Mirrors and Macros simultaneously to derive a type class instance?
You'll have to write a macro working with methods named like <init>$default$1, <init>$default$2, ... in companion object
import scala.quoted.*
inline def printDefaults[T]: Unit = ${printDefaultsImpl[T]}
def printDefaultsImpl[T](using Quotes, Type[T]): Expr[Unit] = {
import quotes.reflect.*
(1 to 3).map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
).foreach(println)
'{()}
}
printDefaults[Foo]
//Some(Literal(Constant(bar)))
//None
//Some(Select(Ident(Math),PI))
Mirrors and macros can work together:
import scala.quoted.*
import scala.deriving.*
trait Default[T] {
type Out <: Tuple
def defaults: Out
}
object Default {
transparent inline given mkDefault[T](using
m: Mirror.ProductOf[T],
s: ValueOf[Tuple.Size[m.MirroredElemTypes]]
): Default[T] =
new Default[T] {
type Out = Tuple.Map[m.MirroredElemTypes, Option]
def defaults = getDefaults[T](s.value).asInstanceOf[Out]
}
inline def getDefaults[T](inline s: Int): Tuple = ${getDefaultsImpl[T]('s)}
def getDefaultsImpl[T](s: Expr[Int])(using Quotes, Type[T]): Expr[Tuple] = {
import quotes.reflect.*
val n = s.asTerm.underlying.asInstanceOf[Literal].constant.value.asInstanceOf[Int]
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
)
def exprOfOption[T](oet: Option[Expr[T]])(using Type[T], Quotes): Expr[Option[T]] = oet match {
case None => Expr(None)
case Some(et) => '{Some($et)}
}
val exprs: List[Option[Expr[Any]]] = terms.map(_.map(_.asExprOf[Any]))
val exprs1: List[Expr[Option[Any]]] = exprs.map(exprOfOption)
Expr.ofTupleFromSeq(exprs1)
}
}
Usage:
val d = summon[Default[Foo]]
summon[d.Out =:= (Option[String], Option[Int], Option[Double])] // compiles
d.defaults // (Some(bar),None,Some(3.141592653589793))
As Dmytro suggests, information is carried in methods <init>default$x of the class companion object.
However, Quotes discourages accessing a symbol's tree in a macro:
https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala#L3628.
Symbol's tree is lost, unless program is compiled with -Yretain-trees)
It is better to let the macro evaluate <init>default$x, rather than copy the right hand side of its definition.
One can do so by expressing terms as :
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.map(Select(Ref(TypeRepr.of[T].typeSymbol.companionModule),_))
)

How to write custom circe codec for Map[String, Any]

Is it possible to write custom Decoder for Map[String, Any] using circe? i've found this, but it's only conversion to Json:
def mapToJson(map: Map[String, Any]): Json =
map.mapValues(anyToJson).asJson
def anyToJson(any: Any): Json = any match {
case n: Int => n.asJson
case n: Long => n.asJson
case n: Double => n.asJson
case s: String => s.asJson
case true => true.asJson
case false => false.asJson
case null | None => None.asJson
case list: List[_] => list.map(anyToJson).asJson
case list: Vector[_] => list.map(anyToJson).asJson
case Some(any) => anyToJson(any)
case map: Map[String, Any] => mapToJson(map)
}
This Circe decoder will convert its Json into Map[String, Any]:
import io.circe.JavaDecoder._
implicit val objDecoder: Decoder[Any] = {
case x: HCursor if x.value.isObject =>
x.value.as[java.util.Map[String, Any]]
case x: HCursor if x.value.isString =>
x.value.as[String]
case x: HCursor if x.value.isBoolean =>
x.value.as[Boolean]
case x: HCursor if x.value.isArray =>
x.value.as[java.util.List[Any]]
case x: HCursor if x.value.isNumber =>
x.value.as[Double]
case x: HCursor if x.value.isNull =>
x.value.as[Unit]}
import io.circe.parser._
parse("""{"foo": 1, "bar": null, "baz": {"a" : "hi", "b" : true, "dogs" : [{ "name" : "Rover", "age" : 4}, { "name" : "Fido", "age" : 5 }] } }""")
.getOrElse(Json.Null)
.as[Map[String, _]]. // <-- this is the call to convert from Circe Json to the Java Map
It would be nicer to case match on the cursor value, but those classes are not public.
Note that I encoded null as Unit, which isn't quite right -- Scala and null don't play nicely and you may need to tailor your implementation to your use case. I actually omit that case entirely from my own implementation because I'm decoding Json that was encoded from Scala instances.
You also need to create a custom Decoder in io.circe for Java Map and List. I did the following, which uses package-private Circe code, making it concise but also vulnerable to changes in Circe and forcing you to have your own io.circe package.
package io.circe
import java.util.{List => JavaList, Map => JavaMap}
import scala.collection.mutable
import scala.collection.immutable.{List => ScalaList, Map => ScalaMap}
import scala.jdk.CollectionConverters.{MapHasAsJava, SeqHasAsJava}
object JavaDecoder {
implicit final def decodeJavaList[A](implicit decodeA: Decoder[A]): Decoder[JavaList[A]] = new SeqDecoder[A, JavaList](decodeA) {
final protected def createBuilder(): mutable.Builder[A, JavaList[A]] =
ScalaList.newBuilder[A].mapResult(_.asJava)
}
implicit final def decodeJavaMap[K, V](implicit
decodeK: KeyDecoder[K],
decodeV: Decoder[V]
): Decoder[JavaMap[K, V]] = new JavaMapDecoder[K, V, JavaMap](decodeK, decodeV) {
final protected def createBuilder(): mutable.Builder[(K, V), JavaMap[K, V]] =
ScalaMap.newBuilder[K, V].mapResult(_.asJava)
}
}
package io.circe
import scala.collection.{Map => ScalaMap}
import scala.collection.mutable
import scala.jdk.CollectionConverters.MapHasAsJava
abstract class JavaMapDecoder[K, V, M[K, V] <: java.util.Map[K, V]](
decodeK: KeyDecoder[K],
decodeV: Decoder[V]
) extends Decoder[M[K, V]] {
private val mapDecoder = new MapDecoder[K, V, ScalaMap](decodeK, decodeV) {
override protected def createBuilder(): mutable.Builder[(K, V), ScalaMap[K, V]] =
ScalaMap.newBuilder[K, V]
}
override def apply(c: io.circe.HCursor): io.circe.Decoder.Result[M[K,V]] =
mapDecoder.apply(c).map(_.asJava.asInstanceOf[M[K, V]])
}
Fwiw, I'm already thinking of submitting this as a PR to Circe.
import io.circe.syntax.EncoderOps
import io.circe.{Encoder, Json}
case class Person(name: String, age: Int)
object Person {
implicit val decoder: io.circe.Decoder[Person] = io.circe.generic.semiauto.deriveDecoder
implicit val encoder: io.circe.Encoder[Person] = io.circe.generic.semiauto.deriveEncoder
}
case class Home(area: Int)
object Home {
implicit val decoder: io.circe.Decoder[Home] = io.circe.generic.semiauto.deriveDecoder
implicit val encoder: io.circe.Encoder[Home] = io.circe.generic.semiauto.deriveEncoder
}
def jsonPrinter[A](obj: A)(implicit encoder: Encoder[A]): Json =
obj.asJson
jsonPrinter(Person("Eminem", 30))
jsonPrinter(Home(300))
This is done with generics, I hope it helps

Scala - Get string representation of object property name, not value, for comparison

I want to be able to get the string representation of an objects property name, not the properties value, so that I can compare it with a variables value inside a conditional statement.
case class CustomObj(name: T)
case class PropertyObj(property: String)
val custObj = CustomObj("Chris")
val propObj = PropertyObj("name")
if(propObj.property.equals(custObj. /* the property name as a String, so "name", not the value ("Chris"*/)) {
// do something
}
How can I access what is essentially the key of the property on the CustomObj?
Try productElementNames like so
case class CustomObj(name: String)
case class PropertyObj(property: String)
val custObj = CustomObj("Chris")
val propObj = PropertyObj("name")
if (custObj.productElementNames.toList.headOption.contains(propObj.property)) { ... } else { ... }
Addressing the comment, based on Krzysztof, try shapeless solution
import shapeless._
import shapeless.ops.record._
def firstPropertyNameOf[P <: Product, L <: HList](p: P)(implicit
gen: LabelledGeneric.Aux[P, L],
toMap: ToMap[L]): Option[String] = {
toMap(gen.to(p)).map{ case (k: Symbol, _) => k.name }.toList.headOption
}
firstPropertyNameOf(custObj).contains(propObj.property) // res1: Boolean = true
I will assume you don't know the type of custObj at compile time. Then you'll have to use runtime reflection in Scala 2.12.
scala> case class CustomObj(name: String)
defined class CustomObj
scala> val custObj: Any = CustomObj("Chris")
custObj: Any = CustomObj(Chris)
scala> import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.currentMirror
scala> val sym = currentMirror.classSymbol(custObj.getClass)
sym: reflect.runtime.universe.ClassSymbol = class CustomObj
scala> val props = sym.info.members.collect{ case m if m.isMethod && m.asMethod.isCaseAccessor => m.name.toString }
props: Iterable[String] = List(name)
scala> if (props.exists(_ == "name")) println("ok")
ok

How instantiate case class from Map[String, String]

Lets say I have
case class Sample(i:Int, b:Boolean)
and
Map[String, String]("i" => "1", "b" => "false")
What is most concise way to instantiate any case class(if all fields are in map), signature like this:
get[T](map:[String, String]) : T
Probably shapeless can help acomplish this task, but I am almost unfamiliar with it. Thanks!
Firstly you can transform Map[String, String] into Map[Symbol, Any] and then use type classes shapeless.ops.maps.FromMap (or extention method .toRecord) and LabelledGeneric:
import shapeless.LabelledGeneric
import shapeless.record.Record
import shapeless.syntax.std.maps._
case class Sample(i: Int, b: Boolean)
val rec = Map('i -> 1, 'b -> false).toRecord[Record.`'i -> Int, 'b -> Boolean`.T].get
LabelledGeneric[Sample].from(rec) //Sample(1,false)
If not using Shapeless is an option for you, you could do it in a simple way without it. Here is a sample implementation where I use the Try Monad and a pattern matching to transform the Map into your case class:
(Try("1".toInt), Try("false".toBoolean)) match {
case (Success(intVal), Success(boolVal)) =>
Sample(intVal, boolVal)
case _ => // Log and ignore the values
}
Of course this is a bit verbose than the Shapeless version, but if you do not want to use a full library just for this simple use case, you could always do it using Scala library!
guys. Probably I wasn't too concise. What I really need is Generic parser from Map of Strings to case class. Thanks for pointing out to LabelledGeneric. Here is solution:
import shapeless.labelled.{FieldType, field}
import shapeless.{::, HList, HNil, LabelledGeneric, Lazy, Witness}
object FieldParser {
trait ValParser[A] {
def parse(str:String): A
}
trait FieldParser[A] {
def parse: A
}
type FT[A, B] = FieldType[A, B]
type ListField[K <: Symbol, A] = FieldType[K, List[A]]
type FP[A] = FieldParser[A]
type Args = (List[String],Map[String, Int])
type Named[K <: Symbol] = Witness.Aux[K]
private def create[A](thunk: A): FP[A] = {
new FP[A] {
def parse: A = thunk
}
}
def apply[A](implicit st: Lazy[FP[A]]): FP[A] = st.value
implicit def genericParser[A, R <: HList](implicit generic: LabelledGeneric.Aux[A, R], parser: Lazy[FP[R]], args:Args): FP[A] = {
create(generic.from(parser.value.parse))
}
implicit def hlistParser[K <: Symbol, H, T <: HList](implicit hParser: Lazy[FP[FT[K, H]]], tParser: FP[T]): FP[FT[K, H] :: T] = {
create {
val hv = hParser.value.parse
val tv = tParser.parse
hv :: tv
}
}
implicit def standardTypeParser[K <: Symbol, V:ValParser](implicit named: Named[K], args:Args): FP[FieldType[K, V]] = {
create(field[K](implicitly[ValParser[V]].parse(findArg)))
}
implicit def optionParser[V](implicit valParser:ValParser[V]): ValParser[Option[V]] = new ValParser[Option[V]]{
def parse(str:String):Option[V] = {
str.isEmpty match {
case true => None
case false => Some(valParser.parse(str))
}
}
}
implicit def listParser[V](implicit valParser:ValParser[V]): ValParser[List[V]] = new ValParser[List[V]]{
def parse(str:String):List[V] = {
str.isEmpty match {
case true => Nil
case false => str.split(",").map(valParser.parse).toList
}
}
}
implicit def doubleParser: ValParser[Double] = new ValParser[Double]{
def parse(str:String):Double = str.toDouble
}
implicit def intParser: ValParser[Int] = new ValParser[Int]{
def parse(str:String):Int = str.toInt
}
implicit def strParser: ValParser[String] = new ValParser[String]{
def parse(str:String):String = str
}
implicit def boolParser: ValParser[Boolean] = new ValParser[Boolean]{
def parse(str:String):Boolean = str.toBoolean
}
implicit val hnilParser: FP[HNil] = create(HNil)
private def findArg[K <: Symbol](implicit args:Args, named: Named[K]): String = {
val name = named.value.name
val index = args._2(name)
args._1(index)
}
}

Generic String interpolator using StringContext

I'm trying to create some simple custom String interpolator, and I'm successful as long as I don't try to use a type parameter.
import scala.concurrent.Future
object StringImplicits {
implicit class FailureStringContext (val sc : StringContext) extends AnyVal {
// This WORKS, but it's specific to Future :(
def fail[T](args : Any*): Future[T] = {
val orig = sc.s (args : _*)
Future.exception[T](new Exception(orig))
}
// I want this to work for Option,Try,Future!!
def fail[M,T](args:Any*): M[T] = {
val orig = sc.s (args : _*)
// Obviously does not work..
M match {
case Future => Future.exception(new Exception(orig))
case Option => None
case Try => Failure(new Exception(orig))
case _ => ???
}
}
}
}
Can I get this to work? I can't use parametric polymorphism because I'm not the one defining those three types.
What's the equivalent in the type level for that pseudo-code pattern match?
LATEST ATTEMPT
My latest attempt was to use implicitly, but I don't have such implicit! I'd be actually interested to grab a hold of the type that the compiler wants me to return according to type inference.
def fail[T, M[T]](args:Any*): M[T] = {
val orig = sc.s(args: _*)
implicitly[M[T]] match {
case _:Future[T] => Future.exception(new Exception(orig))
case _ => ???
}
}
<console>:18: error: could not find implicit value for parameter e: M[T]
implicitly[M[T]] match {
^
<console>:19: error: value exception is not a member of object scala.concurrent.Future
case _: Future[T] => Future.exception(new Exception(orig))
^
In my opinion the simplest is to rely on good old overloading: just define a different overload for each type that you want to handle.
Now of course, there is the problem of having different overloads with the same signature, and as usual in scala, you can use tricks to work around them. Here we'll add dummy implicit parameters to force each overload to have a distinct signature. Not pretty but it works and will suffice in this case.
import scala.concurrent.Future
import scala.util.{Try, Failure}
implicit class FailureStringContext (val sc : StringContext) extends AnyVal {
def fail[T](args : Any*): Future[T] = {
Future.failed[T](new Exception(sc.s (args : _*)))
}
def fail[T](args : Any*)(implicit dummy: DummyImplicit): Option[T] = {
Option.empty[T]
}
def fail[T](args : Any*)(implicit dummy: DummyImplicit, dummy2: DummyImplicit): Try[T] = {
Failure[T](new Exception(sc.s (args : _*)))
}
}
And tada:
scala> fail"oops": Option[String]
res6: Option[String] = None
scala> fail"oops": Future[String]
res7: scala.concurrent.Future[String] = scala.concurrent.impl.Promise$KeptPromise#6fc1a8f6
scala> fail"oops": Try[String]
res8: scala.util.Try[String] = Failure(java.lang.Exception: oops)