Skip to content

Instantly share code, notes, and snippets.

@markus1189
Created March 24, 2017 11:09
Show Gist options
  • Save markus1189/a3bc26a84d493e4a16e5fc82be60718a to your computer and use it in GitHub Desktop.
Save markus1189/a3bc26a84d493e4a16e5fc82be60718a to your computer and use it in GitHub Desktop.
Generic csv encoder with shapeless
// for ammonite
// import $ivy.`com.chuusai::shapeless:2.3.2`
import shapeless._
import shapeless.labelled._
import shapeless.ops.hlist._
import shapeless.ops.record._
case class IceCream(flavour: String, price: Int)
trait CsvEncoder[A] {
def encode(value: A): List[String]
}
object Symboler extends Poly1 {
implicit def default[A <: Symbol](implicit witness: Witness.Aux[A]) =
at[A](_ => witness.value.name)
}
object CsvEncoder {
// "Summoner" method
def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] = enc
// "Constructor" method
def instance[A](func: A => List[String]): CsvEncoder[A] = new CsvEncoder[A] {
def encode(value: A): List[String] =
func(value)
}
def createEncoder[A](func: A => List[String]): CsvEncoder[A] =
new CsvEncoder[A] {
def encode(value: A): List[String] = func(value)
}
implicit val stringEncoder: CsvEncoder[String] = createEncoder(
str => List(str))
implicit val intEncoder: CsvEncoder[Int] = createEncoder(
num => List(num.toString))
implicit val booleanEncoder: CsvEncoder[Boolean] = createEncoder(
bool => List(if (bool) "yes" else "no"))
implicit val hnilEncoder: CsvEncoder[HNil] = createEncoder(hnil => Nil)
implicit def hlistEncoder[H, T <: HList](
implicit hEncoder: CsvEncoder[H],
tEncoder: CsvEncoder[T]): CsvEncoder[H :: T] =
createEncoder {
case h :: t => hEncoder.encode(h) ++ tEncoder.encode(t)
}
implicit def genericEncoder[A, R](implicit gen: Generic.Aux[A, R],
enc: CsvEncoder[R]): CsvEncoder[A] =
createEncoder(a => enc.encode(gen.to(a)))
}
object Main {
def main(args: Array[String]): Unit = {
println(
writeCsv(
List(IceCream("strawberry", 130),
IceCream("lemon", 140),
IceCream("chocolate", 130))))
}
def writeCsv[A, H <: HList, I <: HList, J <: HList](values: List[A])(
implicit enc: CsvEncoder[A],
gen: LabelledGeneric.Aux[A, H],
keys: Keys.Aux[H, I],
mapper: Mapper.Aux[Symboler.type, I, J],
toTraversable: ToTraversable.Aux[J, List, String]): String = {
keys().map(Symboler).toList.mkString(",") + "\n" +
values.map(value => enc.encode(value).mkString(",")).mkString("\n")
}
}
// for ammonite
// Main.main(Array.empty)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment