# kotlin-csv
Pure Kotlin Multiplatform CSV reader and writer.
## Setup
### Gradle (Kotlin DSL)
```kotlin
implementation("com.jsoizo:kotlin-csv:2.0.0")
```
The multiplatform artifact resolves JVM, JS, Kotlin/Wasm, and supported
Kotlin/Native variants from Kotlin Multiplatform projects. Published
Native targets are `macosArm64`, `iosArm64`, `iosSimulatorArm64`,
`linuxX64`, `linuxArm64`, and `mingwX64`. Published Wasm targets are
`wasmJs` and `wasmWasi`.
Single-platform Gradle projects can also depend on the platform artifact
directly, for example `kotlin-csv-jvm`, `kotlin-csv-js`,
`kotlin-csv-wasm-js`, `kotlin-csv-macosarm64`, or `kotlin-csv-linuxx64`.
### Gradle (Groovy DSL)
```groovy
implementation 'com.jsoizo:kotlin-csv:2.0.0'
```
### Maven
```xml
com.jsoizo
kotlin-csv-jvm
2.0.0
com.jsoizo
kotlin-csv-js
2.0.0
```
### SNAPSHOT builds
Snapshots of the next development version are published to
[Sonatype Central Portal Snapshots](https://central.sonatype.com/repository/maven-snapshots/) from the active development branch.
SNAPSHOTs are unstable and may change at any time, but they let you try in-progress changes early.
Add the snapshots repository to your build, then declare the dependency with the corresponding `-SNAPSHOT` version
(see the active development branch's `build.gradle.kts` for the current value, for example `2.0.0-SNAPSHOT`).
#### Gradle (Kotlin DSL)
```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
maven("https://central.sonatype.com/repository/maven-snapshots/") {
mavenContent { snapshotsOnly() }
}
}
}
```
```kotlin
// build.gradle.kts
implementation("com.jsoizo:kotlin-csv:-SNAPSHOT")
```
## Quick start
The DSL builders return reusable, stateless instances; create them once and
share them across calls.
```kotlin
import com.jsoizo.kotlincsv.csvReader
import com.jsoizo.kotlincsv.csvWriter
import com.jsoizo.kotlincsv.reader.CsvNullFieldIndicator
import com.jsoizo.kotlincsv.reader.readFromFile
import com.jsoizo.kotlincsv.reader.withHeader
import com.jsoizo.kotlincsv.writer.WriteQuoteMode
import com.jsoizo.kotlincsv.writer.writeToFile
val reader = csvReader()
val writer = csvWriter()
```
### Read
```kotlin
// From a String — eager
val rows: List> = reader.readAll("a,b,c\nd,e,f")
// From a File — lambda owns the open resource and closes it on exit
reader.readFromFile(File("data.csv")) { rows ->
rows.forEach { println(it) }
}
// With a header row (returns LinkedHashMap to preserve column order)
reader.readFromFile(File("data.csv")) { rows ->
val records = rows.withHeader().toList()
println(records.first()["id"])
}
// Nullable reads can distinguish "" from an unquoted empty field.
val nullableRows = csvReader {
nullFieldIndicator = CsvNullFieldIndicator.EMPTY_SEPARATORS
}.readAllNullable("\"empty\",\"null\"\n\"\",")
```
`reader.readFromFile(...)` accepts `String` paths, `kotlinx.io.files.Path`,
and (on JVM) `java.io.File`. The block receives a cold
`Sequence>`; `take(n)` and friends short-circuit cleanly
without parsing the rest of the file. For in-memory streams use
`reader.read(source)` (commonMain `kotlinx.io.Source`) or
`reader.read(stream)` (JVM `java.io.InputStream`).
### Write
```kotlin
val rows = listOf(
listOf("a", "b", "c"),
listOf("d", "e", "f"),
)
// To a String — eager
val csv: String = writer.writeAll(rows)
// To a File
writer.writeToFile(rows, File("out.csv"))
// Nullable writes emit null as an unquoted empty field.
val nullableCsv = csvWriter {
quoteMode = WriteQuoteMode.ALL
}.writeAllNullable(listOf(listOf(null, "", "value")))
```
`writer.writeToFile(...)` accepts `String` paths, `kotlinx.io.files.Path`,
and (on JVM) `java.io.File`. For in-memory streams use
`writer.write(rows, sink)` (commonMain `kotlinx.io.Sink`) or
`writer.write(rows, stream)` (JVM `java.io.OutputStream`). Both
`Sequence>` and `List>` are accepted as the row
source.
## Configuration
Reader and writer share a `CsvDialect` value object that holds the four
characters defining the CSV format itself: `delimiter`, `quoteChar`,
`escapeChar`, `lineTerminator`. Format-independent policies stay on the
respective `Config`.
```kotlin
val tsvReader = csvReader {
dialect = CsvDialect.TSV
skipEmptyLine = true
}
val customWriter = csvWriter {
dialect = CsvDialect(delimiter = ';', escapeChar = '\\')
quoteMode = WriteQuoteMode.ALL
}
```
| Reader option | Default | Description |
| --- | --- | --- |
| `dialect` | `CsvDialect.RFC4180` | Shared CSV format (delimiter / quote / escape / line terminator). |
| `skipEmptyLine` | `false` | Drop rows that are entirely empty before the field-count check. |
| `excessFieldsRowBehaviour` | `ERROR` | What to do when a row has more fields than the first row: `ERROR` / `IGNORE` / `TRIM`. |
| `insufficientFieldsRowBehaviour` | `ERROR` | What to do when a row has fewer fields: `ERROR` / `IGNORE` / `EMPTY_STRING`. |
| `nullFieldIndicator` | `NEITHER` | Which empty fields nullable reader APIs expose as `null`: `NEITHER` / `EMPTY_SEPARATORS` / `EMPTY_QUOTES` / `BOTH`. |
`CsvFieldNumDifferentException.rowNum` counts CSV rows after reader filters
such as `skipEmptyLine`; it is not a physical source line number.
Nullable reader APIs (`readNullable`, `readAllNullable`, and nullable I/O
overloads) return `String?` values. `withNullableHeader()` keeps header keys
as non-null `String`; a null-looking empty header is treated as the empty
header name `""`, while data values remain nullable.
| Writer option | Default | Description |
| --- | --- | --- |
| `dialect` | `CsvDialect.RFC4180` | Shared CSV format (delimiter / quote / escape / line terminator). |
| `outputLastLineTerminator` | `true` | Emit a trailing line terminator after the final row. |
| `quoteMode` | `CANONICAL` | When to wrap fields in `quoteChar`: `CANONICAL` (only when needed), `ALL`, or `NON_NUMERIC`. |
Charset is JVM-only and is passed as an argument on the I/O call:
```kotlin
reader.readFromFile(File("data.csv"), charset = "Shift_JIS") { it.toList() }
writer.writeToFile(rows, File("out.csv"), charset = "UTF-16LE")
```
`commonMain` and JS overloads are UTF-8 only.
BOM stripping on read defaults to ON (matches Excel-produced files):
```kotlin
reader.readFromFile(File("data.csv"), options = CsvReadIoOptions(stripBom = false))
writer.writeToFile(rows, File("out.csv"), options = CsvWriteIoOptions(prependBom = true))
```
## More
- **Migration from kotlin-csv 1.x**:
see [V2_MIGRATION_GUIDE.md](./V2_MIGRATION_GUIDE.md).
- **API documentation**: generated by Dokka — `./gradlew dokkaHtml` outputs
HTML to `build/dokka/html/`.
- **Change Logs**: see [GitHub releases](https://github.com/jsoizo/kotlin-csv/releases).
## Miscellaneous
### 🤝 Contributing
Contributions, [issues](https://github.com/jsoizo/kotlin-csv/issues) and feature requests are welcome!
If you have questions, ask away in [Kotlin Slack's](https://kotlinlang.slack.com) `kotlin-csv` room.
### 💻 Development
```sh
git clone git@github.com:jsoizo/kotlin-csv.git
cd kotlin-csv
./gradlew check
```
### 📝 License
Copyright © 2024 [jsoizo](https://github.com/jsoizo).
This project is licensed under [Apache 2.0](LICENSE).
### Acknowledgments
This project was originally created by [@doyaaaaaken](https://github.com/doyaaaaaken). The initial work and contributions are greatly appreciated.