Scala : Apache Commons CSVを使う

scala専用のcsvパーサーは特に公式にあるわけではないが、JavaのライブラリにApache Commons CSVがあるのでこれを使える。 Apache Commons CSVは他と比べ高機能な印象。メンテナンスも今後心配ないように思う。

他のCSVパーサーにはOpenCSVなどがある。OpenCSVはライブラリが軽量なのが特徴。

ライブラリの読み込み

build.sbt

libraryDependencies += "org.apache.commons" % "commons-csv" % "1.5",

プログラム

MyCSVParser.scala

import org.apache.commons.csv._
import java.nio.charset.Charset
import java.io.File
import scala.collection.JavaConversions._

object MyCSVParser {

    def parse[A](file: File, mapDefinition: CSVRecord => A): List[A] = {
        val parser = CSVParser.parse(file, Charset.forName("UTF-8"), CSVFormat.DEFAULT.withHeader())
        val list = parser.getRecords.toList.map(mapDefinition)
        list
    }

}

Main.scala

import java.io.File

object Main extends App {

    class Position(val id: Int, val name: String, val posX: Double, val posY: Double) {
        override def toString = "id: %d, name: %s, posX: %f, posY: %f".format(id, name, posX, posY)
    }

    val csvFile = new File("positions.csv")
    val list = MyCSVParser.parse(csvFile, r => new Position(
        r.get("id").toInt, r.get("name"), r.get("posX").toDouble, r.get("posY").toDouble
    ))

    list.foreach(println)

}

positions.csv

id,name,posX,posY
1,テスト1,0.1,0.9
2,テスト2,0.2,0.8
3,テスト3,0.3,0.7
4,テスト4,0.4,0.6

terminal output

id: 1, name: テスト1, posX: 0.100000, posY: 0.900000
id: 2, name: テスト2, posX: 0.200000, posY: 0.800000
id: 3, name: テスト3, posX: 0.300000, posY: 0.700000
id: 4, name: テスト4, posX: 0.400000, posY: 0.600000

備考

  • MyCSVParser.parse での副作用を避けるために、Fileオブジェクトの生成はMainで行い、引数に渡すようにした。
  • それぞれの行をどのように処理するかは、CSVRecord => A型の高階関数を渡すようにした。これは便利。
  • CSVParser.getRecords()はjava.util.List型を返すが、そのままではscalaで扱えない。 scala.collection.JavaConversions._を用いればscalaのリストとして扱える。 暗黙の型変換が定義されており、getRecords.toListの間で java.util.List -> mutable.Buffer -> List と変換されている。
  • csvファイルのフォーマットやヘッダの有無は CSVFormat の部分で変更できる。

todo

以下の時にエラーとなるのを回避する方法が知りたい

  • csvファイルのヘッダーに空文字列があるとき
  • 空文字列に対して.toDoubleなど指定するとき(例えば行末にゴミデータがある時など)

参考