Read CSV line-by-line in Kotlin

Viewed 36378

I'm writing a simple import application and need to read a CSV file, show result in a grid and show corrupted lines of the CSV file in another grid.

Is there any built-in lib for it or any easy pythonic-like way?

I'm doing it on android.

7 Answers

[Edit October 2019] A couple of months after I wrote this answer, Koyama Kenta wrote a Kotlin targeted library which can be found at https://github.com/doyaaaaaken/kotlin-csv and which looks much better to me than opencsv.

Example usage: (for more info see the github page mentioned)

import com.github.doyaaaaaken.kotlincsv.dsl.csvReader

fun main() {
    csvReader().open("src/main/resources/test.csv") {
        readAllAsSequence().forEach { row ->
            //Do something
            println(row) //[a, b, c]
        }
    }
}

For a complete minimal project with this example, see https://github.com/PHPirates/kotlin-csv-reader-example

Old answer using opencsv:

As suggested, it is convenient to use opencsv. Here is a somewhat minimal example:

// You can of course remove the .withCSVParser part if you use the default separator instead of ;
val csvReader = CSVReaderBuilder(FileReader("filename.csv"))
        .withCSVParser(CSVParserBuilder().withSeparator(';').build())
        .build()

// Maybe do something with the header if there is one
val header = csvReader.readNext()

// Read the rest
var line: Array<String>? = csvReader.readNext()
while (line != null) {
    // Do something with the data
    println(line[0])

    line = csvReader.readNext()
}

As seen in the docs when you do not need to process every line separately you can get the result in the form of a Map:

import com.opencsv.CSVReaderHeaderAware
import java.io.FileReader

fun main() {
    val reader = CSVReaderHeaderAware(FileReader("test.csv"))
    val resultList = mutableListOf<Map<String, String>>()
    var line = reader.readMap()
    while (line != null) {
        resultList.add(line)
        line = reader.readMap()
    }
    println(resultList)
    // Line 2, by column name
    println(resultList[1]["my column name"])
}

Dependency for Gradle: compile 'com.opencsv:opencsv:4.6' or for Gradle Kotlin DSL: compile("com.opencsv:opencsv:4.6") (as always, check for latest version in docs).

In terms of easiness, kotlin written csv library is better.

For example, you can write code in DSL like way with below library that I created:

https://github.com/doyaaaaaken/kotlin-csv

csvReader().open("test.csv") {
    readAllAsSequence().forEach { row ->
        //Do something with the data
        println(row)
    }
}

Frankly speaking, it is quite easy to make a simple reader in Kotlin using modern Java features, check this (REMEMBER to handle BOM :-)):

fun processLineByLine(csv: File, processor: (Map<String, String>) -> Unit)  {
    val BOM = "\uFEFF"
    val header = csv.useLines { it.firstOrNull()?.replace(BOM, "")?.split(",") }
            ?: throw Exception("This file does not contain a valid header")

    csv.useLines { linesSequence ->
        linesSequence
                .drop(1)
                .map { it.split(",") }
                .map { header.zip(it).toMap() }
                .forEach(processor)
    }
}

Than you can use it as follows (depends on your file structure):

processLineByLine(File("./my-file.csv")) { row ->
    println("UserId: ${row["userId"]}")
    println("Email: ${row["email"]}")
}

If you prefer to use your own data class for each row you should have a look at my solution https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/csv/CSVTable.kt

data class User(
        val name: String,
        val phone: String,
        val email: String
) {
    constructor(columns: List<String>) : this(
            name = columns[0],
            phone = columns[1],
            email = columns[2]
    )
}

CSVTable.print(FileInputStream("users.csv")) 
val userList = CSVTable(FileInputStream("users.csv"), ::User).rows

I know i'm a bit late, but I recently had problems with parsing CSV and there seemed to be no library good enough for what I was looking for, so I created my own called Kotlin CSV stream.

This library is special because it doesn't throw exceptions on an invalid input, but returns in the result instead, which might be useful in some cases.

Here is an example of how easy it is to use

val reader = CsvReader()
    .readerForType<CsvPerson>()
val people = reader.read(csv).map { it.getResultOrThrow() }.toList()
Related