DEV Community

Cover image for  Kotlin CSV to Data Class Parser
Victor Harlan D. Lacson
Victor Harlan D. Lacson

Posted on • Updated on

Kotlin CSV to Data Class Parser

Update: The current version is 0.8.0

Features

1. Simple And Direct

  • No hard configuration
  • No invasive annotations to data class
  • Custom mapping
  • Nullable Data Types

2. Primitive Types

  • Short
  • Int
  • Long
  • Float
  • Double
  • Boolean
  • String

3. Support for Java 8 Date Time Apis

  • LocalTime
  • LocalDateTime
  • LocalDate
  • Custom Formatting

4. Support Custom Data Type Transformation

see documentation


Hello Everyone!!

Hi to those who love Kotlin

To this date, I haven't found one unique implementation on Kotlin CSV parser into Kotlin data class.

So I decided to create one, and it is now available on GitHub Kotlin-Grass.

Unlike some existing Java CSV parsers, it doesn't require to invade your data class with annotation to map CSV file columns to data class fields.

It requires another awesome Kotlin library for reading the CSV, namely Kotlin-CSV. Since this one is implemented in Kotlin Multiplatform I don't intend to reinvent the wheel for reading CSV File.

Let's get back to my Kotlin-Grass, a CSV to data class parser. It is already available at Maven Central for download.

repositories {
  mavenCentral()
}

dependencies {
   //doyaaaaaken's kotlin-csv
   implementation("com.github.doyaaaaaken:kotlin-csv-jvm:0.15.2")
   //kotlin-grass
   implementation("io.github.blackmo18:kotlin-grass-core-jvm:1.0.0")
   implementation("io.github.blackmo18:kotlin-grass-parser-jvm:0.8.0")")
}
Enter fullscreen mode Exit fullscreen mode

Its usage is quite simple and direct nothing more than a plug and play

for example, we have a CSV file with the following data

short,int,long,float,double,boolean,string
0,1,2,3.0,4.0,true,hello
Enter fullscreen mode Exit fullscreen mode

the data class would simply look like

data class PrimitiveTypes(
    val short: Short,
    val int: Int,
    val long: Long,
    val float: Float,
    val double: Double,
    val boolean: Boolean,
    val string: String
)
Enter fullscreen mode Exit fullscreen mode

Yes! No annotation is required. It will automatically map the first line as header to data class fields.

The usage is just like

val csvContents = csvReader().readAllWithHeader(file)
val dataClasses = grass<PrimitiveTypes>().harvest(csvContents)
Enter fullscreen mode Exit fullscreen mode

or the other way

csvReader.open(file) {
    val csvContents = readAllWithHeaderAsSequence()
    val dataClasses = grass<PrimitiveTypes>().harvest(csvContents)
}
Enter fullscreen mode Exit fullscreen mode

You get a list or sequence of object parsed into a data class.

Also your PRs and feature requests are much appreciated if you had any issues.

Happy Hacking

Latest comments (7)

Collapse
 
dgolembomyob profile image
Danel Golembo

Hi, are you planning to move this awesome project off jcenter and to maven central, given that jcenter is being deprecated? Thanks in advance.

Collapse
 
blackmo18 profile image
Victor Harlan D. Lacson

Yes, this is already been moved, i haven't updated this post for a while but the GitHub is updated. I will update this one shortly

Collapse
 
stephanatwhich profile image
Stephan Schroeder

Cool, csv-parsing is a nice, self-contained but useful problem domain!
I recently wrote a csv to Kotlin data class parser for my work at 'Which?' as well 😅. I called my project KSV.

CSV contain some interesting corner cases that you might want to consider:

  • column names (the comma-separated elements of the header) might contain spaces, so while the property name is a good default, you might want to add an annotation that let's you define a different name (e.g. one that contains a space)
  • column names and values might be surrounded by quotes so that they can contain a comma (so your line splitting has to be a bit smarter than simply splitting by comma )
  • you don't seem to support nullable types currently, it makes sense to map empty strings in the csv to null, if the type you map to is nullable
  • while the separator in csv is often a comma and the quote a double quote, it's a good idea to make that configurable (e.g. because german uses a comma instead of a dot for floats, their csv often uses a semicolon instead)

advanced features:

  • support conversion to LocalDateTime (you need to specify the expected format)
  • support conversion to user-defined types (the user must be able to add string-to- converters)

I thing those are the main additional features I can think of right now. How did you publish to jcenter? Is it free (for open source)? I tried to google a free jar repository, but ended up suggesting Gradle's source dependency mechanism

Collapse
 
blackmo18 profile image
Victor Harlan D. Lacson

Hi, thanks and I really appreciate the key points you shared.

  • Regarding the custom delimiters, surrounded with quotes, Kotlin-Csv already handles the scenarios. That's why I mainly focused on my Kotlin-Grass as a parser itself.

  • Custom Mapping is also supported on initial release, instead of relying on annotation I resorted to DSL mappings.

 val grass = grass<DateTimeTypes> {
        customKeyMap = mapOf("hour" to "time", "birthdate" to "date")
    }

which is I think neat and there is no need to annotate your data class

  • Support on Java Date Time API conversion is also available.
  • On User-defined types, I never yet encountered a common use case. I guess the user can do extension functions instead without polluting data class of unwanted logic but data itself. If it is not that complicated 😖🤣

With the release of version 0.3.0, nullable values, and white spaces were already covered.

Publishing to jcenter I never thought would be just easy, although I have done so much research since my structure is for kotlin-multiplatform. You just need to create bintray account, then publish your library in there. Once your library is uploaded you can request to link your packages to jcenter.

Apply necessary plugin in Gradle build file, like maven publish. You can check my repo's build file, it is configured for publishing in bintray.

Collapse
 
grodzickir profile image
Ryszard Grodzicki

I'd propose an enhancement to the customKeyMap, or implementing another parameter for stating the mapping in more kotlin way with KProperty:

val grass = grass<DateTimeTypes> {
        customKeyMap = mapOf("hour" to DateTimeTypes::time, "birthdate" to DateTimeTypes::date)
    }
Enter fullscreen mode Exit fullscreen mode

Nonetheless - great job 👍

Thread Thread
 
blackmo18 profile image
Victor Harlan D. Lacson • Edited

I agree with your suggestion, ill keep this in mind in the future release.

github.com/blackmo18/kotlin-grass/...

Collapse
 
stephanatwhich profile image
Stephan Schroeder

cool, seems like you got all the features I can think of then 👍