I'm trying to make a CSV (with headers) parser that extracts a row into a case class
. I want the extraction to rely on the header to affect (instead of relying on the case class
parameters being in the same order as in the CSV) the value to the right fields. I'm using magnolia to do the deserializing part. To test the magnolia, I feed the deserializer a Map
containing the CSV content.
I'm calling the decoder like that:
case class PlayerData(btag: String, team: String, status: String, role: String)
val csv = new CSV(Array(Map(
("btag" -> "btag#value"),
("team" -> "team_name"),
("status" -> "player_status"),
("role" -> "player role"),
)))
val ps = csv.extract[PlayerData]
for(PlayerData(b, t, _, r) <- ps) {
println(s"btag: $b, team: $t, role: $r")
}
The implementation of the decoder is the following:
object Decoding {
object LineDecoder {
implicit class DecoderOps[A: LineDecoder](p: Map[String, String]) {
def decode: A = implicitly[LineDecoder[A]].decode(p)
}
type Typeclass[T] = LineDecoder[T]
def combine[T](caseClass: CaseClass[LineDecoder, T]): LineDecoder[T] = new LineDecoder[T] {
def cols: Int = caseClass.parameters.map(_.typeclass.cols).sum
def decode(p: Map[String, String]): T = {
@annotation.tailrec
def make_param_list(row: Map[String, String],
cc_params: Seq[Param[Typeclass, T]],
c_params: Vector[Any]): T = {
if(cc_params.isEmpty) {
caseClass.rawConstruct(c_params)
} else {
val ctor_param = cc_params.head
val tail = cc_params.tail
val param_value = row.get(ctor_param.label).get
make_param_list(row, tail, c_params :+ param_value)
}
}
make_param_list(p, caseClass.parameters, Vector())
}
}
def apply[T](fn: Map[String, String] => T, len: Int = 1) = new LineDecoder[T] {
def decode(p: Map[String, String]): T = fn(p)
def cols: Int = len
}
implicit def gen[T]: LineDecoder[T] = macro Magnolia.gen[T]
}
trait LineDecoder[T] {
def cols: Int
def decode(p: Map[String, String]): T
}
}
class CSV(csv: Array[Map[String, String]]) {
import Decoding._
def extract[T: LineDecoder](): ArraySeq[T] = csv.map( line => {
implicitly[LineDecoder[T]].decode(line)
} )
}
It was heavily inspired by caesura.
When compiling, I have this error:
[error] dev/scala/team-stats/src/main/scala/test.scala:67:25: could not find implicit value for evidence parameter of type Decoding.LineDecoder[CLI.PlayerData]
[error] val ps = csv.extract[PlayerData]
What am I doing wrong?
Put
implicit val strLineDecoder: LineDecoder[String] = ??? // your implementation
to the companion object of LineDecoder
.
Magnolia can derive instances of a type class for case classes and sealed traits but can't guess them for other types.
Also def extract[T: LineDecoder : ClassTag](): Array[T] = ...
should be instead of def extract[T: LineDecoder](): ArraySeq[T] = ...
.