Этот случай хорошо описан Type Astronaut
книга: https://books.underscore.io/shapeless-guide/shapeless-guide.html#records -and-labelledgeneri c.
Итак, в общем, вам нужно взглянуть на LabeledGeneric
тип класса - которые помогают с производными любого типа продукта (например, класса дел).
В вашем случае реализация может выглядеть так:
import shapeless.{::, HList, HNil, LabelledGeneric, Lazy, Witness}
import shapeless.labelled.FieldType
trait Encoder[T] {
def encode(value: T): String
}
object Encoder {
def createEncoder[T](f: T => String): Encoder[T] = f(_)
def apply[T](implicit encoder: Encoder[T]): Encoder[T] = encoder
implicit val stringEncoder: Encoder[String] = createEncoder[String](identity)
implicit val intEncoder: Encoder[Int] = createEncoder[Int](_.toString)
}
// Special encoder, which can encode product or object
trait ObjectEncoder[T] extends Encoder[T] {
final override def encode(value: T): String = {
encodeObject(value).map{
case (key, value) => s"$key=$value"
}.mkString("&")
}
def encodeObject(t: T): Map[String, String]
}
object ObjectEncoder {
def createEncoder[T](f: T => Map[String, String]): ObjectEncoder[T] = f(_)
def apply[T](implicit encoder: ObjectEncoder[T]): ObjectEncoder[T] = encoder
// This need to terminate derivation process
implicit val hNilObjectEncoder: ObjectEncoder[HNil] = {
ObjectEncoder.createEncoder(_ => Map.empty)
}
// Generate object encoder derivation
implicit def hlistObjectEncoder[K <: Symbol, H, T <: HList]
(implicit
fieldWitness: Witness.Aux[K],
headEncoder: Lazy[Encoder[H]],
tailEncoder: ObjectEncoder[T]
): ObjectEncoder[FieldType[K, H] :: T] = {
val fieldName: String = fieldWitness.value.name
ObjectEncoder.createEncoder { hlist =>
val headValue: String = headEncoder.value.encode(hlist.head)
val head: Map[String, String] = List(fieldName -> headValue).toMap
val tail: Map[String, String] = tailEncoder.encodeObject(hlist.tail)
head ++ tail
}
}
implicit def genericObjectEncoder[A, H]
( implicit
generic: LabelledGeneric.Aux[A, H],
hEncoder: Lazy[ObjectEncoder[H]]
): ObjectEncoder[A] = {
ObjectEncoder.createEncoder { value =>
val t = generic.to(value)
hEncoder.value.encodeObject(t)
}
}
}
// Just syntactic sugar helpers
object EncoderSyntax {
implicit class EncodeOps[A](a: A) {
def encode(implicit encoder: Encoder[A]): String = encoder.encode(a)
}
}
def main(args: Array[String]): Unit = {
import Encoder._
import ObjectEncoder._
import EncoderSyntax._
case class Example(foo: String, bar: String)
val example = Example("foo", "bar")
println(example.encode)
}
, что приведет к следующему результату:
foo=foo&bar=bar
Надеюсь, это поможет!