kantan.csv
型安全なCSVライブラリ。Scalaの型システムを活用してコンパイル時にCSVデータの型チェックを実行。自動的なケースクラスマッピング、カスタムエンコーダ・デコーダ定義により安全で効率的なCSV処理を実現。
フレームワーク
kantan.csv
概要
kantan.csvは、Scalaの型システムを活用した型安全なCSVライブラリです。コンパイル時のCSVデータ型チェック、自動ケースクラスマッピング、カスタムエンコーダ・デコーダ定義により、安全で効率的なCSV処理を実現します。
詳細
kantan.csv(カンタンシーエスブイ)は、ScalaでのタイプセーフなCSV処理を目的として2016年に開発されたライブラリです。Scalaの強力な型システムを活用し、コンパイル時にCSVデータの型安全性を保証することで、実行時エラーを大幅に削減します。最大の特徴は、ケースクラスとCSVの間の自動マッピング機能で、最小限のボイラープレートコードでCSVの読み書きが可能です。型クラスベースの設計により、カスタムデータ型に対するエンコーダ・デコーダの定義が直感的に行えます。Shapeless ライブラリを使用した自動派生により、複雑なデータ構造でも型安全なCSV操作を実現。エラーハンドリングも型安全で、パースエラーや型変換エラーを適切に処理できます。関数型プログラミングの原則に従い、イミュータブルなデータ構造と純粋関数によるAPIを提供します。
メリット・デメリット
メリット
- 型安全性: コンパイル時の型チェックによる実行時エラーの削減
- 自動マッピング: ケースクラスとCSVの自動変換
- カスタマイズ可能: 柔軟なエンコーダ・デコーダの定義
- 関数型API: 純粋関数による予測可能な動作
- 豊富な型サポート: 標準型とカスタム型の幅広いサポート
- エラーハンドリング: 型安全なエラー処理
デメリット
- 学習コスト: 型クラスの理解が必要
- コンパイル時間: 型レベル計算によるコンパイル時間増加
- エラーメッセージ: 型エラーのメッセージが複雑
- パフォーマンス: 型安全性のためのオーバーヘッド
主な使用事例
- データ分析前処理
- ETL処理
- ログファイル解析
- 設定ファイル処理
- レポート生成
- データ検証
- 金融データ処理
基本的な使い方
依存関係の追加
libraryDependencies += "com.nrinaudo" %% "kantan.csv" % "0.7.0"
基本的なCSV読み込み
import kantan.csv._
import kantan.csv.ops._
// CSVファイルの読み込み
val csvFile = new java.io.File("data.csv")
// 文字列として読み込み
val stringReader = csvFile.asCsvReader[String](',', true)
val strings = stringReader.toVector
// 型安全な読み込み(タプル)
val tupleReader = csvFile.asCsvReader[(String, Int, Double)](',', true)
val tuples = tupleReader.toVector
// エラーハンドリング付きの読み込み
tuples.foreach {
case Success((name, age, salary)) =>
println(s"Name: $name, Age: $age, Salary: $salary")
case Failure(error) =>
println(s"Error: ${error.getMessage}")
}
ケースクラスとの自動マッピング
import kantan.csv._
import kantan.csv.ops._
import kantan.csv.generic._
// データ構造の定義
case class Employee(name: String, age: Int, salary: Double, department: String)
// 自動マッピングでの読み込み
val employees = new java.io.File("employees.csv")
.asCsvReader[Employee](',', true)
.toVector
// 成功したレコードのみ取得
val validEmployees = employees.collect {
case Success(employee) => employee
}
println(s"Valid employees: ${validEmployees.size}")
validEmployees.foreach(println)
カスタムデータ型のサポート
import kantan.csv._
import kantan.csv.ops._
import java.time.LocalDate
import java.time.format.DateTimeFormatter
// カスタム型のデコーダ定義
implicit val localDateDecoder: CellDecoder[LocalDate] = {
CellDecoder.from(str =>
Try(LocalDate.parse(str, DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.toEither
.left.map(ex => ParseError(s"Invalid date: $str", ex))
)
}
// カスタム型のエンコーダ定義
implicit val localDateEncoder: CellEncoder[LocalDate] = {
CellEncoder.from(_.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
}
// カスタム型を含むケースクラス
case class Person(name: String, birthDate: LocalDate, isActive: Boolean)
// 使用例
val people = new java.io.File("people.csv")
.asCsvReader[Person](',', true)
.toVector
val validPeople = people.collect {
case Success(person) => person
}
validPeople.foreach(println)
CSV書き込み
import kantan.csv._
import kantan.csv.ops._
// データの準備
val employees = List(
Employee("Alice", 30, 50000.0, "IT"),
Employee("Bob", 25, 45000.0, "Sales"),
Employee("Charlie", 35, 60000.0, "IT")
)
// ファイルへの書き込み
val outputFile = new java.io.File("output.csv")
val writer = outputFile.asCsvWriter[Employee](',', true)
// ヘッダー付きで書き込み
writer.write(employees)
writer.close()
// 単一レコードの書き込み
val singleWriter = outputFile.asCsvWriter[Employee](',', false)
employees.foreach(singleWriter.write)
singleWriter.close()
高度なCSV操作
import kantan.csv._
import kantan.csv.ops._
import kantan.csv.generic._
// 複雑なデータ構造
case class Address(street: String, city: String, zipCode: String)
case class EmployeeWithAddress(
name: String,
age: Int,
salary: Double,
address: Address
)
// ネストした構造のデコーダ
implicit val addressDecoder: RowDecoder[Address] = RowDecoder.decoder(2, 3, 4)(Address.apply)
implicit val employeeWithAddressDecoder: RowDecoder[EmployeeWithAddress] =
RowDecoder.decoder(0, 1, 2, Address.apply)(EmployeeWithAddress.apply)
// 条件付きフィルタリング
val highSalaryEmployees = new java.io.File("employees.csv")
.asCsvReader[Employee](',', true)
.toVector
.collect {
case Success(employee) if employee.salary > 50000 => employee
}
// グループ化
val employeesByDepartment = highSalaryEmployees.groupBy(_.department)
employeesByDepartment.foreach { case (dept, emps) =>
println(s"Department: $dept, Count: ${emps.size}")
}
エラーハンドリングと検証
import kantan.csv._
import kantan.csv.ops._
// カスタムバリデーション
case class ValidatedEmployee(name: String, age: Int, salary: Double) {
require(name.nonEmpty, "Name cannot be empty")
require(age > 0 && age < 120, "Age must be between 1 and 119")
require(salary > 0, "Salary must be positive")
}
// バリデーション付きデコーダ
implicit val validatedEmployeeDecoder: RowDecoder[ValidatedEmployee] =
RowDecoder.ordered { (name: String, age: Int, salary: Double) =>
try {
Success(ValidatedEmployee(name, age, salary))
} catch {
case ex: IllegalArgumentException =>
Failure(ParseError(ex.getMessage, ex))
}
}
// エラー詳細の処理
val results = new java.io.File("employees.csv")
.asCsvReader[ValidatedEmployee](',', true)
.toVector
val (errors, validEmployees) = results.partition(_.isFailure)
println(s"Valid records: ${validEmployees.size}")
println(s"Invalid records: ${errors.size}")
errors.foreach {
case Failure(error) => println(s"Error: ${error.getMessage}")
case _ => // Never reached
}
ストリーミング処理
import kantan.csv._
import kantan.csv.ops._
import scala.util.Using
// 大きなCSVファイルのストリーミング処理
def processLargeCSV(filename: String): Unit = {
Using(new java.io.File(filename).asCsvReader[Employee](',', true)) { reader =>
reader
.filter(_.isSuccess)
.map(_.get)
.filter(_.salary > 50000)
.grouped(100) // バッチ処理
.foreach { batch =>
println(s"Processing batch of ${batch.size} employees")
// バッチ処理ロジック
batch.foreach(employee => {
// 個別処理
println(s"Processing ${employee.name}")
})
}
}
}
processLargeCSV("large_employees.csv")
設定とカスタマイズ
import kantan.csv._
import kantan.csv.ops._
// カスタムCSV設定
val customConfig = rfc.withCellSeparator(';').withQuote('"')
// 設定を使用した読み込み
val customReader = new java.io.File("semicolon_separated.csv")
.asCsvReader[Employee](customConfig)
// ヘッダーマッピング
case class PersonWithHeader(firstName: String, lastName: String, age: Int)
implicit val personHeaderDecoder: HeaderDecoder[PersonWithHeader] =
HeaderDecoder.decoder("first_name", "last_name", "age")(PersonWithHeader.apply)
val headerBasedReader = new java.io.File("with_headers.csv")
.asCsvReader[PersonWithHeader](rfc.withHeader)
.toVector
// 空セルの処理
case class OptionalData(name: String, age: Option[Int], salary: Option[Double])
val optionalReader = new java.io.File("optional_data.csv")
.asCsvReader[OptionalData](',', true)
.toVector
optionalReader.foreach {
case Success(data) => println(s"Name: ${data.name}, Age: ${data.age}, Salary: ${data.salary}")
case Failure(error) => println(s"Error: ${error.getMessage}")
}
最新のトレンド(2025年)
- Scala 3対応: 最新の型システム機能の活用
- パフォーマンス向上: ZIOやCats Effect統合
- ストリーミング強化: 大規模データ処理対応
- バリデーション機能: refined型との統合
- JSON統合: CSV-JSON変換の簡素化
まとめ
kantan.csvは2025年において、Scalaでの型安全なCSV処理の標準的な選択肢として確立されています。型安全性、自動マッピング、カスタマイズ可能性により、堅牢で保守しやすいCSV処理アプリケーションを構築できます。特に金融・保険業界での厳密なデータ処理要件に対応した実用的なライブラリです。