Search code examples
scalacsvmagnolia-scala

How to fix an issue "could not find implicit value for evidence parameter of type" when using magnolia


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?


Solution

  • 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] = ....