Skip to content

Instantly share code, notes, and snippets.

@jto
Created August 26, 2016 12:38
Show Gist options
  • Save jto/af42ddac763d050a4182bf33534511cc to your computer and use it in GitHub Desktop.
Save jto/af42ddac763d050a4182bf33534511cc to your computer and use it in GitHub Desktop.
Automatic case class to CSV derivation using shapeless
case class Foo(i: Int)
case class Bar(foo: Foo, g: String)
val b = Bar(Foo(1), "toto")
val h = ToHList(b) // List[shapeless.::[Int, shapeless.::[String, shapeless.HNil]]] = List(1 :: toto :: HNil)
Ser.toCSV(h) // "1,toto"
import shapeless.{ HList, Nat, Sized, Poly1 }
object Ser {
import shapeless.ops.hlist.{ Mapper, ToTraversable, Length }
import com.github.tototoshi.csv._
import java.io.StringWriter
object asString extends Poly1 {
implicit def opt[T] = at[Option[T]](_.map(_.toString).getOrElse(""))
implicit def default[T] = at[T](_.toString)
}
def write(ls: List[List[String]]) = {
val sw = new StringWriter()
val writer = CSVWriter.open(sw)
writer.writeAll(ls)
writer.close()
sw.toString
}
def toCSV[L <: Nat](headers: Sized[List[String], L]) =
write(headers.unsized :: Nil)
def toCSV[H <: HList, MOut <: HList](hs: List[H])(implicit
mapper: Mapper.Aux[asString.type, H, MOut],
toList: ToTraversable.Aux[MOut, List, String]): String = {
val lines = toStrings(hs)
write(lines)
}
def toStrings[H <: HList, MOut <: HList](hs: List[H])(implicit
mapper: Mapper.Aux[asString.type, H, MOut],
toList: ToTraversable.Aux[MOut, List, String]): List[List[String]] = {
for { h <- hs }
yield h.map(asString).toList
}
def toCSV[H <: HList, MOut <: HList, L <: Nat](headers: Sized[List[String], L], hs: List[H])(implicit
mapper: Mapper.Aux[asString.type, H, MOut],
toList: ToTraversable.Aux[MOut, List, String],
length: Length.Aux[H, L]): String = toCSV(headers) ++ toCSV(hs)
}
import shapeless.{ HNil, Lazy, ::, HList, Nat, Generic, Poly1 }
trait ToHList[T] {
type Out <: HList
def to(t: T): List[Out]
}
private[tabular] trait LowPriorityToHList {
type Aux[T, Out0 <: HList] = ToHList[T] { type Out = Out0 }
implicit def default[T]: Aux[T, T :: HNil] =
new ToHList[T] {
type Out = T :: HNil
def to(s: T): List[Out] = List(s :: HNil)
}
}
object ToHList extends LowPriorityToHList {
import shapeless.ops.hlist._
def apply[T](implicit th: ToHList[T]): Aux[T, th.Out] = th
def apply[T](t: T)(implicit th: ToHList[T]): List[th.Out] = apply[T](th).to(t)
trait LowPriorityFlattenOpts extends Poly1 {
implicit def default[T] = at[T] { t => Option(t) }
}
object flattenOpts extends LowPriorityFlattenOpts {
implicit def caseOpt[T] = at[Option[T]] { t => t }
}
import scalaz.{ Tag, @@ }
implicit def taggedToHList[A, T](implicit th: Lazy[ToHList[A]]): Aux[A @@ T, th.value.Out] =
new ToHList[A @@ T] {
type Out = th.value.Out
def to(a: A @@ T): List[th.value.Out] =
th.value.to(Tag.unwrap(a))
}
implicit def listToHlist[T, TOut <: HList, OOut <: HList, L <: Nat](implicit
th: Lazy[ToHList.Aux[T, TOut]],
m: Lazy[Mapper[flattenOpts.type, TOut]],
length: Lazy[Length.Aux[TOut, L]],
fill: Lazy[Fill[L, None.type]]): Aux[List[T], m.value.Out] =
new ToHList[List[T]] {
implicit val mapper = m.value
type Out = m.value.Out
def to(ts: List[T]): List[m.value.Out] =
if (ts.isEmpty)
List(fill.value(None).asInstanceOf[m.value.Out])
else for {
t <- ts
h <- th.value.to(t)
} yield h.map(flattenOpts).asInstanceOf[Out]
}
implicit def seqToHlist[T](implicit lToH: Lazy[ToHList[List[T]]]): Aux[Seq[T], lToH.value.Out] =
new ToHList[Seq[T]] {
type Out = lToH.value.Out
def to(ts: Seq[T]): List[lToH.value.Out] =
lToH.value.to(ts.toList)
}
implicit def optionToHlist[T, H <: HList, L <: Nat](
implicit
toH: Lazy[ToHList.Aux[T, H]],
mapper: Lazy[Mapper[flattenOpts.type, H]],
length: Lazy[Length.Aux[H, L]],
fill: Lazy[Fill[L, None.type]]
): Aux[Option[T], mapper.value.Out] =
new ToHList[Option[T]] {
type Out = mapper.value.Out
def to(opt: Option[T]): List[Out] =
opt match {
case None =>
List(fill.value(None).asInstanceOf[Out])
case Some(t) =>
for { h <- toH.value.to(t) }
yield h.map(flattenOpts)(mapper.value)
}
}
implicit val hniltoHList: Aux[HNil, HNil] =
new ToHList[HNil] {
type Out = HNil
def to(h: HNil) = List(HNil)
}
implicit def genToHlist[T, TOut <: HList](implicit gen: Lazy[Generic.Aux[T, TOut]], toHlist: Lazy[ToHList[TOut]]): Aux[T, toHlist.value.Out] =
new ToHList[T] {
type Out = toHlist.value.Out
def to(t: T): List[toHlist.value.Out] = toHlist.value.to(gen.value.to(t))
}
implicit def hlisttoHList[H, T <: HList, HOut <: HList, TOut <: HList](implicit h: Lazy[Aux[H, HOut]], ts: Lazy[Aux[T, TOut]], prepend: Lazy[Prepend[HOut, TOut]]): Aux[H :: T, prepend.value.Out] =
new ToHList[H :: T] {
implicit val p = prepend.value
type Out = prepend.value.Out
def to(hs: H :: T): List[prepend.value.Out] =
for {
head <- h.value.to(hs.head)
tail <- ts.value.to(hs.tail)
} yield (head ::: tail).asInstanceOf[Out]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment