21
votes

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
I suggest marking one of the responses below as having answered your question so it's clear to the thousands of viewers. I think @KoyamaKenta who uses github.com/doyaaaaaken/kotlin-csv is a great solution. Even PHPirate recommended that as a solution.Vahid Pazirandeh

7 Answers

14
votes

[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).

9
votes

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)
    }
}
5
votes

Use opencsv.

This is gonna work like a charm for reading a CSV file.

As far as logging the corrupted lines is concerned you can do it using this logic.

while(input.hasNextLine())
{
    try 
    {
         //execute commands by reading them using input.nextLine()
    }
    catch (ex: UserDefinedException)
    {
         //catch/log the exceptions you're throwing
         // log the corrupted line the continue to next iteration
    }
}

Hope this helps.

5
votes

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"]}")
}
3
votes

I used net.sourceforge.javacsv with my Kotlin code for parsing CSV files. It is a "java" library but within kotlin it is fairly straightforward to work with it like

val reader = CsvReader("/path/to/file.csv").apply {
  trimWhitespace = true
  skipEmptyRecords = true
  readHeaders()
}

while (reader.readRecord()) {
  // do whatever
}
2
votes

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
1
votes

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()