# kotlin-csv

Version License: Apache License 2.0 codecov CodeFactor

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.