Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make case-class⇄csv-row conversion robust to public fields #2

Merged
merged 1 commit into from
Dec 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
make case-class⇄csv-row conversion robust to public fields
  • Loading branch information
ryan-williams committed Dec 4, 2016
commit 4848e1578890e429e458414118ab6166eea0145f
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
name := "string-utils"
version := "1.0.0"
version := "1.1.0"

libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
11 changes: 11 additions & 0 deletions src/main/scala/org/hammerlab/csv/CaseClassUtil.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.hammerlab.csv

import scala.reflect.runtime.universe._

object CaseClassUtil {
// cf. http://stackoverflow.com/a/16079804/544236
def methodNames[T <: Product: TypeTag]: List[String] =
typeOf[T].members.sorted.collect {
case m: MethodSymbol if m.isCaseAccessor => m
} map(_.name.toString)
}
31 changes: 9 additions & 22 deletions src/main/scala/org/hammerlab/csv/ProductsToCSV.scala
Original file line number Diff line number Diff line change
@@ -1,37 +1,24 @@
package org.hammerlab.csv

import org.hammerlab.csv.CaseClassUtil.methodNames
import org.hammerlab.csv.ProductToCSVRow._

import scala.reflect.ClassTag
import scala.reflect.runtime.universe.TypeTag

class ProductsToCSV[T <: Product: TypeTag](products: Iterator[T]) {

class ProductsToCSV[T <: Product: ClassTag](products: Iterator[T]) {
def toCSV(includeHeaderLine: Boolean = true): Iterator[String] =
if (includeHeaderLine) {
val (clazz, firstOpt) =
if (products.hasNext) {
val first = products.next()
(first.getClass, Some(first))
} else {
val ctag = implicitly[reflect.ClassTag[T]]
(ctag.runtimeClass.asInstanceOf[Class[T]], None)
}

val headerLine =
clazz
.getDeclaredFields
.map(_.getName)
.filterNot(_ == "$outer") // Field added on case classes nested inside another class.
.mkString(",")
val headerLine = methodNames[T].mkString(",")

Iterator(headerLine) ++
firstOpt.map(_.toCSV).iterator ++
toCSV(includeHeaderLine = false)
Iterator(headerLine) ++ toCSV(includeHeaderLine = false)
} else
products.map(_.toCSV)
}

object ProductsToCSV {
implicit def apply[T <: Product: ClassTag](products: Iterator[T]): ProductsToCSV[T] = new ProductsToCSV(products)
implicit def apply[T <: Product: ClassTag](products: Iterable[T]): ProductsToCSV[T] = new ProductsToCSV(products.iterator)
implicit def apply[T <: Product: ClassTag](products: Array[T]): ProductsToCSV[T] = new ProductsToCSV(products.iterator)
implicit def apply[T <: Product: TypeTag](products: Iterator[T]): ProductsToCSV[T] = new ProductsToCSV(products)
implicit def apply[T <: Product: TypeTag](products: Iterable[T]): ProductsToCSV[T] = new ProductsToCSV(products.iterator)
implicit def apply[T <: Product: TypeTag](products: Array[T]): ProductsToCSV[T] = new ProductsToCSV(products.iterator)
}
4 changes: 3 additions & 1 deletion src/test/scala/org/hammerlab/csv/ProductsToCSVTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ class ProductsToCSVTest
with Matchers {

// cf. http://stackoverflow.com/questions/30271823/converting-a-case-class-to-csv-in-scala
case class Person(name: String, age: Int, address: Option[String])
case class Person(name: String, age: Int, address: Option[String]) {
val foo: Int = age * age
}

test("people") {
val ppl =
Expand Down