Created
August 26, 2016 12:38
-
-
Save jto/af42ddac763d050a4182bf33534511cc to your computer and use it in GitHub Desktop.
Automatic case class to CSV derivation using shapeless
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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