Skip to content

Commit 97abe22

Browse files
authored
SCALA-268 (#1760)
* SCALA-268 Scala CSV Read/Write * Naming convention fixed
1 parent 7a0849f commit 97abe22

16 files changed

+458
-1
lines changed

Diff for: build.sbt

+5-1
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,11 @@ lazy val scala_libraries = (project in file("scala-libraries"))
442442
"org.apache.logging.log4j" % "log4j-core" % "2.24.3" % Runtime,
443443
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.5",
444444
"software.amazon.awssdk" % "s3" % "2.25.9",
445-
"com.github.seratch" %% "awscala" % "0.9.2"
445+
"com.github.seratch" %% "awscala" % "0.9.2",
446+
"com.opencsv" % "opencsv" % "5.9",
447+
"com.github.tototoshi" %% "scala-csv" % "2.0.0",
448+
"org.apache.commons" % "commons-csv" % "1.12.0"
449+
446450
),
447451
libraryDependencies ++= Seq(
448452
"org.playframework" %% "play-slick" % LibraryVersions.playSlickVersion,

Diff for: scala-libraries/src/main/resources/persons.csv

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
id,name
2+
1,Jim
3+
2,Bob
4+
3,Mary
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.baeldung.csv.reader
2+
3+
import org.apache.commons.csv.CSVFormat
4+
5+
import java.io.{File, FileInputStream, InputStreamReader}
6+
import scala.jdk.CollectionConverters.IterableHasAsScala
7+
8+
class ApacheCommonsCSVReader extends CommaSeparatedValuesReader {
9+
10+
override def read(file: File): CSVReadDigest = {
11+
val in = new InputStreamReader(new FileInputStream(file))
12+
val csvParser = CSVFormat.DEFAULT
13+
.builder()
14+
.setHeader()
15+
.build()
16+
.parse(in)
17+
val result = CSVReadDigest(
18+
csvParser.getHeaderNames.asScala.toSeq,
19+
csvParser.getRecords.asScala.map(r => r.values().toSeq).toSeq
20+
)
21+
csvParser.close()
22+
result
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.baeldung.csv.reader
2+
3+
import java.io.File
4+
5+
object CSVReaders {
6+
7+
def main(args: Array[String]): Unit = {
8+
9+
val apacheCommonsCSVReader = new ApacheCommonsCSVReader
10+
val simpleCSVReader = new SimpleCSVReader
11+
val scalaCSVReader = new ScalaCSVReader
12+
val openCSVReader = new OpenCSVReader
13+
14+
val file = new File(getClass.getResource("/persons.csv").getFile)
15+
16+
println(apacheCommonsCSVReader.read(file))
17+
println(simpleCSVReader.read(file))
18+
println(scalaCSVReader.read(file))
19+
println(openCSVReader.read(file))
20+
21+
}
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.baeldung.csv.reader
2+
3+
import java.io.File
4+
5+
case class CSVReadDigest(headers: Seq[String], rows: Seq[Seq[String]])
6+
trait CommaSeparatedValuesReader {
7+
8+
def read(file: File): CSVReadDigest
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.baeldung.csv.reader
2+
3+
import com.opencsv.CSVReader
4+
5+
import java.io.{File, FileInputStream, InputStreamReader}
6+
import scala.annotation.tailrec
7+
8+
class OpenCSVReader extends CommaSeparatedValuesReader {
9+
10+
override def read(file: File): CSVReadDigest = {
11+
12+
val reader = new CSVReader(
13+
new InputStreamReader(new FileInputStream(file))
14+
)
15+
16+
@tailrec
17+
def readLinesRecursively(
18+
currentReader: CSVReader,
19+
result: Seq[Seq[String]]
20+
): Seq[Seq[String]] = {
21+
currentReader.readNext() match {
22+
case null => result
23+
case line => readLinesRecursively(currentReader, result :+ line.toSeq)
24+
}
25+
}
26+
27+
val csvLines = readLinesRecursively(reader, List())
28+
reader.close()
29+
30+
CSVReadDigest(
31+
csvLines.head,
32+
csvLines.tail
33+
)
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.baeldung.csv.reader
2+
3+
import com.github.tototoshi.csv.CSVReader
4+
5+
import java.io.File
6+
7+
class ScalaCSVReader extends CommaSeparatedValuesReader {
8+
9+
override def read(file: File): CSVReadDigest = {
10+
val reader = CSVReader.open(file)
11+
val all = reader.all()
12+
reader.close()
13+
CSVReadDigest(all.head, all.tail)
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.baeldung.csv.reader
2+
3+
import java.io.{BufferedReader, File, FileInputStream, InputStreamReader}
4+
import scala.annotation.tailrec
5+
6+
class SimpleCSVReader extends CommaSeparatedValuesReader {
7+
8+
override def read(file: File): CSVReadDigest = {
9+
val in = new InputStreamReader(new FileInputStream(file))
10+
val bufferedReader = new BufferedReader(in)
11+
12+
@tailrec
13+
def readLinesRecursively(
14+
currentBufferedReader: BufferedReader,
15+
result: Seq[Seq[String]]
16+
): Seq[Seq[String]] = {
17+
currentBufferedReader.readLine() match {
18+
case null => result
19+
case line =>
20+
readLinesRecursively(
21+
currentBufferedReader,
22+
result :+ line.split(",").toSeq
23+
)
24+
}
25+
}
26+
27+
val csvLines = readLinesRecursively(bufferedReader, List())
28+
29+
bufferedReader.close()
30+
31+
CSVReadDigest(
32+
csvLines.head,
33+
csvLines.tail
34+
)
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.baeldung.csv.writer
2+
import org.apache.commons.csv.{CSVFormat, CSVPrinter}
3+
4+
import java.io.{File, FileWriter}
5+
import scala.util.Try
6+
7+
class ApacheCommonsCSVWriter extends CommaSeparatedValuesWriter {
8+
9+
override def write(
10+
file: File,
11+
headers: Seq[String],
12+
rows: Seq[Seq[String]]
13+
): Try[Unit] = Try {
14+
15+
val csvFormat = CSVFormat.DEFAULT
16+
.builder()
17+
.setHeader(headers: _*)
18+
.build()
19+
20+
val out = new FileWriter(file)
21+
val printer = new CSVPrinter(out, csvFormat)
22+
rows.foreach(row => printer.printRecord(row: _*))
23+
printer.close()
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.baeldung.csv.writer
2+
3+
import java.io.File
4+
import scala.util.Try
5+
6+
trait CommaSeparatedValuesWriter {
7+
8+
def write(
9+
file: File,
10+
headers: Seq[String],
11+
rows: Seq[Seq[String]]
12+
): Try[Unit]
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.baeldung.csv.writer
2+
3+
import java.io.File
4+
import scala.util.{Failure, Success, Try}
5+
6+
object CsvWriters {
7+
8+
def main(args: Array[String]): Unit = {
9+
10+
val fileName = "foo.csv"
11+
val headers = List("id", "name")
12+
val rows = List(
13+
List("1", "Manolis"),
14+
List("2", "Thanasis"),
15+
List("3", "Stefanos")
16+
)
17+
18+
val simpleCSVWriter = new SimpleCSVWriter
19+
val openCSVWriter = new OpenCSVWriter
20+
val scalaCSVWriter = new ScalaCSVWriter
21+
val apacheCommonsCSVWriter = new ApacheCommonsCSVWriter
22+
23+
handleFailure(
24+
simpleCSVWriter.write(new File(s"simple-$fileName"), headers, rows)
25+
)
26+
handleFailure(
27+
openCSVWriter.write(new File(s"openCSV-$fileName"), headers, rows)
28+
)
29+
handleFailure(
30+
scalaCSVWriter.write(new File(s"scalaCSV-$fileName"), headers, rows)
31+
)
32+
handleFailure(
33+
apacheCommonsCSVWriter.write(
34+
new File(s"apacheCommons-$fileName"),
35+
headers,
36+
rows
37+
)
38+
)
39+
}
40+
41+
private def handleFailure(tryWrite: Try[Unit]): Unit = {
42+
tryWrite match {
43+
case Success(_) =>
44+
case Failure(exception) =>
45+
println(
46+
s"Something went wrong during CSV writing: ${exception.getMessage}"
47+
)
48+
}
49+
}
50+
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.baeldung.csv.writer
2+
3+
import com.opencsv.CSVWriter
4+
5+
import java.io.{BufferedWriter, File, FileWriter}
6+
import scala.jdk.CollectionConverters.IterableHasAsJava
7+
import scala.util.Try
8+
9+
class OpenCSVWriter extends CommaSeparatedValuesWriter {
10+
11+
override def write(
12+
file: File,
13+
headers: Seq[String],
14+
rows: Seq[Seq[String]]
15+
): Try[Unit] = Try(
16+
new CSVWriter(new BufferedWriter(new FileWriter(file)))
17+
).flatMap((csvWriter: CSVWriter) =>
18+
Try {
19+
csvWriter.writeAll(
20+
(headers +: rows).map(_.toArray).asJava,
21+
false
22+
)
23+
csvWriter.close()
24+
}
25+
)
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.baeldung.csv.writer
2+
3+
import com.github.tototoshi.csv.CSVWriter
4+
5+
import java.io.File
6+
import scala.util.Try
7+
8+
class ScalaCSVWriter extends CommaSeparatedValuesWriter {
9+
10+
override def write(
11+
file: File,
12+
headers: Seq[String],
13+
rows: Seq[Seq[String]]
14+
): Try[Unit] = Try {
15+
val writer = CSVWriter.open(file)
16+
writer.writeRow(headers)
17+
writer.writeAll(rows)
18+
writer.close()
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.baeldung.csv.writer
2+
3+
import java.io.{File, PrintWriter}
4+
import scala.util.Try
5+
6+
class SimpleCSVWriter extends CommaSeparatedValuesWriter {
7+
8+
override def write(
9+
file: File,
10+
headers: Seq[String],
11+
rows: Seq[Seq[String]]
12+
): Try[Unit] = Try {
13+
val writer = new PrintWriter(file)
14+
writer.println(headers.mkString(","))
15+
rows.foreach(row => writer.println(row.mkString(",")))
16+
writer.close()
17+
}
18+
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.baeldung.csv.reader
2+
3+
import org.scalatest.matchers.should
4+
import org.scalatest.wordspec.AnyWordSpec
5+
6+
import java.io.{File, PrintWriter}
7+
import java.nio.file.{Files, Path}
8+
import java.util.UUID
9+
10+
class ScalaCSVReadersUnitTest extends AnyWordSpec with should.Matchers {
11+
12+
"SimpleCSVReader" should {
13+
"read a csv file" in {
14+
commonTestBody(new SimpleCSVReader, randomTmpFile().toFile)
15+
}
16+
}
17+
18+
"ScalaCSVReader" should {
19+
"read a csv file" in {
20+
commonTestBody(new ScalaCSVReader, randomTmpFile().toFile)
21+
}
22+
}
23+
24+
"OpenCSVReader" should {
25+
"read a csv file" in {
26+
commonTestBody(new OpenCSVReader, randomTmpFile().toFile)
27+
}
28+
}
29+
30+
"ApacheCommonsCSVReader" should {
31+
"read a csv file" in {
32+
commonTestBody(new ApacheCommonsCSVReader, randomTmpFile().toFile)
33+
}
34+
}
35+
36+
private def commonTestBody(
37+
testee: CommaSeparatedValuesReader,
38+
file: File
39+
): Unit = {
40+
val result = testee.read(file)
41+
assert(result.headers === List("column1", "column2"))
42+
assert(
43+
result.rows === List(
44+
List("1.1", "1.2"),
45+
List("2.1", "2.2")
46+
)
47+
)
48+
}
49+
50+
private def randomTmpFile(): Path = {
51+
val path = Files.createTempFile(
52+
s"persons-${UUID.randomUUID().toString.take(8)}",
53+
"csv"
54+
)
55+
val writer = new PrintWriter(path.toFile)
56+
writer.println(List("column1", "column2").mkString(","))
57+
writer.println(List("1.1", "1.2").mkString(","))
58+
writer.println(List("2.1", "2.2").mkString(","))
59+
writer.close()
60+
path
61+
}
62+
63+
}

0 commit comments

Comments
 (0)