Reputation: 22595
I try to create a simple shapeless based function to convert the case class into a list of string I could then encode as csv.
The point of this is to summon type class CsvEncoder
on every member of the case class and then invoke method encode
to change it to string.
Type class:
trait CsvEncoder[T] {
def encode(t: T): String
}
trait LowPriorityEncoder {
implicit def genericEncoder[T]: CsvEncoder[T] = _.toString
}
object CsvEncoder extends LowPriorityEncoder {
implicit def optionEncoder[T](
implicit e: CsvEncoder[T]
): CsvEncoder[Option[T]] = _.fold("")(e.encode)
}
And shapeless:
class CsvBuilder[Row](rows: List[Row]) {
object csvMapper extends Poly1 {
implicit def caseEncode[V](
implicit e: CsvEncoder[V]
): Case.Aux[FieldType[Symbol, V], FieldType[Symbol, String]] =
at[FieldType[Symbol, V]](s => field[Symbol](e.encode(s)))
}
def build[Repr <: HList, OutRepr <: HList](
implicit labelledGeneric: LabelledGeneric.Aux[Row, Repr],
mapper: Mapper.Aux[csvMapper.type, Repr, OutRepr],
toMap: ToMap.Aux[OutRepr, Symbol, String]
): List[Map[String, String]] = {
def transform(row: Row): Map[String, String] = {
val formattedRows = labelledGeneric
.to(row)
.map(csvMapper)
val fields = toMap(formattedRows)
.map {
case (k: Symbol, value: String) =>
(k.toString -> value)
}
fields
}
rows.map(transform(_))
}
}
When I try to use with simple case class:
final case class ProjectCsvRow(
project: Option[String],
something: Option[String],
site: Option[String],
state: Option[Int]
)
new CsvBuilder[ProjectCsvRow](
List(ProjectCsvRow(Some(""), None, None, None))
).build
I'm getting
could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper.Aux[stabilizer$1.csvMapper.type,Repr,OutRepr]
I think I'm missing something, but I can't really figure it out.
I already checked if there's instance of CsvEncoder
for every field.
Upvotes: 1
Views: 729
Reputation: 51658
Firstly, your Poly
is incorrect. Type Symbol
is too rough. Keys of a record are not just Symbol
s, they are singleton subtypes of Symbol
. You should define
object csvMapper extends Poly1 {
implicit def caseEncode[K <: Symbol, V](
implicit e: CsvEncoder[V]
): Case.Aux[FieldType[K, V], FieldType[K, String]] =
at[FieldType[K, V]](s => field[K](e.encode(s)))
}
Secondly, making a Poly
nested into a class (rather than object) can be dangerous (it starts to depend on an instance of CsvBuilder
and therefore on type Row
). So move csvMapper
outside CsvBuilder
(so that its path is stable).
Upvotes: 2