kantan.csv

型安全なCSVライブラリ。Scalaの型システムを活用してコンパイル時にCSVデータの型チェックを実行。自動的なケースクラスマッピング、カスタムエンコーダ・デコーダ定義により安全で効率的なCSV処理を実現。

ScalaCSV型安全データ処理ケースクラスエンコーダデコーダパーサー

フレームワーク

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処理アプリケーションを構築できます。特に金融・保険業界での厳密なデータ処理要件に対応した実用的なライブラリです。