I have defined this in order to serialize any case class whose fields a scalars:
import scala.compiletime.*
import scala.deriving.*
trait Scalar[T]:
def convert(t: T): AnyRef
val sqlType: String
given Scalar[String] with
def convert(t: String) = t
val sqlType = "VARCHAR"
given Scalar[Int] with
def convert(t: Int) = Integer.valueOf(t)
val sqlType = "INTEGER"
trait Serializable[T]:
def serialize(v: T): Map[String, AnyRef]
def fields(v: T): Map[String, String]
object Serializable:
inline def summonAll[T <: Tuple]: List[Scalar[?]] =
inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[Scalar[t]] :: summonAll[ts]
inline given derived[A <: Product](using m: Mirror.ProductOf[A]): Serializable[A] =
val scalars = summonAll[m.MirroredElemTypes]
new Serializable[A]:
override def serialize(v: A): Map[String, AnyRef] =
val pro = v.asInstanceOf[Product]
pro.productElementNames.zip(pro.productIterator).zip(scalars).map {
case ((name, value), scalar) =>
name -> scalar.asInstanceOf[Scalar[Any]].convert(value)
}.toMap
override def fields(v: A): Map[String, String] =
val pro = v.asInstanceOf[Product]
pro.productElementNames.zip(scalars).map {
case (name, scalar) =>
name -> scalar.asInstanceOf[Scalar[Any]].sqlType
}.toMap
case class Person(name: String, age: Int) derives Serializable
And it works thanks to the magic compile time mirrors.
But I need to define this:
trait Serializable[T]:
def serialize(v: T): Map[String, AnyRef]
val fields: Map[String, String]
instead of:
def fields(v: T): Map[String, String]
because the fields depend not on the instance, but on the class type.
The problem is that I don't know how to get field names, when I don't have an instace of the case class.
Here is the solution I've found, maybe it's no the best but it works:
#!/usr/bin/env -S scala-cli -j system
import scala.compiletime.*
import scala.deriving.*
trait Scalar[T]:
def convert(t: T): AnyRef
val sqlType: String
given Scalar[String] with
def convert(t: String) = t
val sqlType = "VARCHAR"
given Scalar[Int] with
def convert(t: Int) = Integer.valueOf(t)
val sqlType = "INTEGER"
trait Serializable[T]:
def serialize(v: T): Map[String, AnyRef]
val fields: Map[String, String]
object Serializable:
inline def summonAll[T <: Tuple]: List[Scalar[?]] =
inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[Scalar[t]] :: summonAll[ts]
inline given derived[A <: Product](using m: Mirror.ProductOf[A]): Serializable[A] =
val scalars = summonAll[m.MirroredElemTypes]
new Serializable[A]:
override def serialize(v: A): Map[String, AnyRef] =
val pro = v.asInstanceOf[Product]
pro.productElementNames.zip(pro.productIterator).zip(scalars).map {
case ((name, value), scalar) =>
name -> scalar.asInstanceOf[Scalar[Any]].convert(value)
}.toMap
override val fields: Map[String, String] =
import scala.compiletime.constValueTuple
val nombres = constValueTuple[m.MirroredElemLabels].productIterator
nombres.zip(scalars).map {
case (name, scalar) =>
name.toString() -> scalar.asInstanceOf[Scalar[Any]].sqlType
}.toMap
case class Person(name: String, age: Int) derives Serializable
val sp = summon[Serializable[Person]]
assert(sp.fields("age") == "INTEGER")