Help us understand the problem. What is going on with this article?

Kotlin で可愛く CSV

More than 1 year has passed since last update.

Kotlin 可愛い!

私は普段 Java や Python を使うエンジニアですが、Kotlin が好きです。

単純なデータの Import/Export にはよく CSV 形式を使用していますが、Kotlin ならどれだけ気持ちよく書けるのか試してみました。

ソースコードはこちらです。→ motch0214/kotlin-csv-sample

Apache commons-csv

今回、CSV のパーサライブラリとして commons-csv を使用します。
単純な機能感のものですが、Kotlin との相性を考えると、むしろ適していると感じます。シンプルに書けるのが Kotlin の魅力のため、Annotation 等でゴテゴテする必要はないです。(個人の見解です。)

サンプルデータ

使用するデータは以下とします。

"Name","Address","Sex","Age","Birthday"
"茂木 康文","mogi_yasufumi@example.com","男",32,"1983-04-25"
"立川 弘也","tachikawa_hironari@example.com","男",69,"1946-05-12"
"宮田 真一","miyata_shinichi@example.com","男",79,"1935-09-13"
"浅田 俊二","asada_shunji@example.com","男",53,"1961-10-29"

ヘッダあり、数値以外はクォートされています。

対応する data class も用意します。

data class User(
        val name: String,
        val address: String,
        val sex: String,
        val age: Int,
        val birthday: LocalDate
)

また、今回はヘッダを指定して CSV 内の列を指定するため、以下のような enum class を使います。

enum class UserColumn(override val header: String) : Column {
    NAME("Name"),
    ADDRESS("Address"),
    SEX("Sex"),
    AGE("Age"),
    BIRTHDAY("Birthday"),
}

Column は単なる header へのアクセス用インターフェースです。

interface Column {
    val header: String
}

CSV の読み込み

では、読み込みロジック部分です。

private val DATE_FORMAT: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")

class UserReader(reader: Reader) : CSVReader<User>(reader) {

    private fun String.toLocalDate(): LocalDate = LocalDate.parse(this, DATE_FORMAT)

    override fun CSVRecord.parse() = User(
            name = column(NAME),
            address = column(ADDRESS),
            sex = column(SEX),
            age = column(AGE).toInt(),
            birthday = column(BIRTHDAY).toLocalDate())
}

なかなか可愛くないですか?

CSV のどの列がどのように User クラスにマッピングされるのかが一目瞭然だと思います。

ちなみに、ベースクラスである CSVReader は私が書いたものですが、数行しかない極小のラッパーです。(→ CSVReader
拡張関数を駆使して、いい感じの見た目になるように調整しています。

CSV の書き込み

次は、書き込みロジック部分です。

class UserWriter(writer: Writer) : CSVWriter<User>(writer, HEADERS) {

    companion object {
        private val HEADERS = UserColumn.values().toList()
    }

    override fun User.encode() = mapOf(
            NAME to name,
            ADDRESS to address,
            SEX to sex,
            AGE to age,
            BIRTHDAY to birthday.format(DATE_FORMAT))
}

読み込み同様、マッピングが一目瞭然になっています。

encode() の返り値は Map にしています。ヘッダの定義とマッピング部分の順番がずれているなんてしょうもないバグに悩まされたくないのです。

CSVWriter も私が書いたものですが、同じく薄いラッパーです。(→ CSVWriter

まとめ

大した道具を使わなくても、Kotlin ならば CSV とオブジェクトのマッピングロジックが明示的でわかりやすく書けたと思います。

わかりやすいのがとっても重要です。
(Javaだとなかなかこうはいきません。。)

みなさんも Kotlin を使って可愛くプログラミングをしてみませんか?

ご意見・ご指摘お待ちしています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away