Search code examples
scalacsv

How do I create a RowDecoder that combines other RowDecoders in Kantan


Using Kantan in Scala, I want to create a RowDecoder[Combined] for a Combined case class that has more than 22 fields. I have created RowDecoder[PartA], RowDecoder[PartB], RowDecoder[PartC], and a method ((PartA, PartB, PartC) => Combined. Does Kantan provide a way to declare the RowDecoder[Combined]? Ideally the decoder handles the input only once (String containing row => Seq[String] with fields). I have created a RowDecoder[Combined] like this:

        implicit val rowDecoder: RowDecoder[Combined] = RowDecoder.from { elems =>
          val line   = elems.mkString(",")
          val (a, b, c) = (partA(line), partB(line), partC(line)) // partA/B/C have a `RowDecoder[PartX]` and do a line.readCsvRow()
          (a, b, c) match {
            case (Left(e), _, _)         => Left(TypeError(e.getMessage))
            case (_, Left(e), _)         => Left(TypeError(e.getMessage))
            case (_, _, Left(e))         => Left(TypeError(e.getMessage))
            case (Right(a), Right(b), Right(c)) => Right(Combined.fromParts(a, b, c))
          }
        }

But that performs multiple String -> Seq[String] -> seq.mkString -> Seq[String] transformations that can hopefully be prevented.


Solution

  • Thanks @MartinHH for pointing me to that issue, I've created the RowDecoder based on the suggestions there:

          implicit val partADecoder: RowDecoder[PartA] = RowDecoder.decoder(0, 1, 3)(PartA.apply)
          implicit val partBDecoder: RowDecoder[PartB] = RowDecoder.decoder(2, 4, 5)(PartB.apply)
          implicit val rowDecoder: RowDecoder[Combined] = RowDecoder.from { elems =>
            for {
              a <- partADecoder.decode(elems)
              b <- partBDecoder.decode(elems)
            } yield Combined(b.colA, a.col1, b.colB, a.col2, b.colC, a.col3)
          }
    
          def combined(in: String): ReadResult[Combined] = in.readCsvRow(rfc)