[ Kotlin ] OpenCSV を使ってCSVの読み書きをする

[ Kotlin ] OpenCSV を使ってCSVの読み書きをする

こんばんは。七色メガネです。

今回は Kotlin で OpenCSV というライブラリを使ってCSVの読み書きをする方法を学びます。

OpenCSV について

OpenCSV ってなに?

OpenCSV は Java 向けに開発された CSV パーサー・ライブラリです。Java7 以降についてサポートされています。Kotlin でも使用することができます。

OpenCSV の特徴は?

OpenCSV では主に次のようなことが実現できます。

  • 囲み文字内の要素の中に含まれるコンマの無視
  • 囲み文字内に改行文字が含まれている場合の処理
  • 区切り文字や囲み文字の指定
  • CSVから配列データへの変換
  • CSVからJavaBeanへの変換

テスト環境

次の環境でテストします。

  • IDE
    IntelliJ IDEA
  • Build Tool
    Gradle
  • FrameWork
    SpringBoot
  • Language
    Kotlin
  • OpenCSV
    ver. 4.6

環境設定

まずは環境を作ります。Gradle と IntelliJ は既に導入済みのものとします。

今回はSpringBootを使用するので、Spring Initializr でプロジェクトを簡易作成しましょう。
ビルドツールとしてGradle, 言語として Kotlin を選ぶ以外は任意の設定で構いません。

https://start.spring.io/

次にIntelliJでプロジェクトを開きましょう。設定では、auto Import を有効にします。

設定を終了し、初期ビルドが成功したら build.gradle に いくつかのライブラリを追加します。

今回追加したいライブラリは次の通りです。

  • OpenCSV
  • spring-boot-starter-web (アノテーション使用のため)

OpenCSV のライブラリ情報は次のサイトで取得します。Gradle タグをクリックするとGradle用の情報が見えます。

https://mvnrepository.com/artifact/com.opencsv/opencsv/4.6

今回追加する設定は
compile group: ‘com.opencsv’, name: ‘opencsv’, version: ‘4.6’
だとわかりました。ではこれを build.gradle.kts に追記しましょう。

build.gradle.kts

これで準備は完了です。

CSVの書き込み

ではOpenCSVを用いてCSVの書き込みを行なっていきます。
今回は次の2パターンを試行します。

  1. 配列データをそのまま書き込む
  2. Writer と クラスBean を紐付けて書き込む

配列データをそのまま書き込む

OpenCSV の CSVWriter を使用することで、特にクラスなどを定義せずともCSVへのデータ書き込みを行うことができます。

まずはテストデータを用意します。今回は便宜上クラスのインスタンスとしてデータを用意していますが、あとで取り出して配列化します。

コントローラーです。特筆事項はないです。

サービスの実装です。

CSVWriter#writeNext は文字配列を引数にとりCSVに書き込む機能を持っているので、インスタンスそれぞれからフィールドを取り出し、配列化した上で渡しています。

string配列に限定されるため数字も文字に変換しています。
また書き込む内容を全て指定する必要もあるので、あまり業務では使いたくない方法ですね。

これにより生成されたCSVがこちらです。

 

Writer と クラスBean を紐付けて書き込む

OpenCSVでは、データクラスとして定義されたBeanを用いてCSV書き込みを行うこともできます。

今回用意したデータクラスはこちらです。今回の方法では変数名がヘッダーとして出力されるので、それも考慮しつつ変数名を設定しておきます。

コントローラに特筆事項はないので、サービスの実装から。

StatefulBeanToCsv は、Bean を CSV に書き込むためのクラスです。同名Builder の build メソッドにを用い、紐付けたいクラスと使用するデータを指定することで生成できます。build の前には、いくつかの設定(区切り文字など)を行うこともできます。

これにより生成されたCSVがこちらです。

先ほど作成されたものとは違い、ヘッダーがありますね。これは紐づけられたデータクラスのフィールド変数名で自動設定されています。

また今回は文字に対するクォート表現を無効にしたので、すっきりと見やすい形になっています。

このように、CSV出力に関する情報をサービス側ではなくクラス側で定義できるため、サービス側の記述を簡易なものにすることができます。

CSVの読み込み

では次に、OpenCSVでの読み込みを行なって見たいと思います。

今回は次の3パターンを試行します。

  1. CSVの内容をそのまま読み込む。
  2. CSVの内容をBeanに紐付けて読み込む。紐付けルールは、CSVのデータ順序に基づく。
  3. CSVの内容をBeanに紐付けて読み込む。紐付けルールは、CSVのヘッダー情報に基づく。

CSVの内容をそのまま読み込む。

まずはCSVをそのまま読み込んで見ます。

読み込むCSVの内容は次の通りです。

サービスの実装はこのようになります。

CSVを直接読み込むには、OpenCSV の csvReader を使用します。ファイルオブジェクトとの紐付けは、上のコードからよしなに読み取ってください。

今回は csvReader#readAll を用いています。これは、紐づけられたCSVを全て読み込み、各行のデータを文字配列とした上で、全行文をリストにまとめて返却する関数です。今回は、 records 変数にサイズ3のリストが格納され、それぞれに各行の文字配列データを収められています。

順序が前後しましたが、サービスを呼び出すコントローラーはこのようになっています。
受け取った文字配列リストを、そのままコンソールに返すことで表示する想定です。

読み取り結果はこちらになります。

各行のデータが読み取れていますね。

CSVの内容をBeanに紐付けて読み込む(ルール:CSVのデータ順序)

OpenCSV には、CSVの中身を指定したデータクラスに紐付けながら読み取ってくれる機能があります。読み取りの後に加工などが入る場合には、こちらがとても便利でしょう。

データクラスに紐付ける場合、紐付けルールを「順序」か「ヘッダー名」で定義できます。ここではまず前者を試行します。

今回の紐付け対象となるデータクラスは次のものです。

各フィールドには、OpenCSV が提供する @CsvBindByPosition というアノテーションを使用します。
これにより、CSV中のデータを指定した順序でそれぞれのフィールドに紐づけることができます。

読み取るデータは先ほどと同じです。

コントローラに特筆事項はありませんが、先ほどの直接読み込みの時はコンソールに文字配列のリストを返却していたのに対し、今回はクラスに紐付けての読み込みができているので、モデルのリストで返却していることがわかります。

最後にサービスの実装です。

CSVをモデルに紐付けて読み取るためには、CsvToBean クラスを使用します。StatefulBeanToCsv の時と同じように、同名の Builder クラスの build でインスタンスを生成します。
Builder へのクラス型指定とは別に、#withType でクラス型を設定する必要があるので注意してください。また #withType の引数は Class<?> なので、クラス名を渡すだけでは不足です。

こうして読み込めたデータが次のものです。

指定した順序通りにCSVを読み込み、クラスの各フィールドと結び付けられていることがわかりますね。

この方法では順序で紐付けを定義しているので、CSVの順序がずれれば紐付けももちろんずれます。
例えば、データクラスでは Name>No>Place で順序を指定しているのに、CSVの中身が No>Place>Name となっていた場合などです。

これは当然、このようにずれた状態で読み取りが行われます。

業務でこのロジックを使う場合には、CSV側のデータ順序が必ずずれないようにルールを設定する必要がありますね。

CSVの内容をBeanに紐付けて読み込む(ルール:CSVのヘッダー情報)

CSVとBeanの紐付け方法には、順序以外に、CSVのヘッダー名称を使用する方法があります。

今回のCSVサンプルはこちらです。

それに対して紐付けを行うデータクラスはこちらです。

先ほどは @CsvBindByPosition アノテーションを使用していましたが、今回は @CsvBindByName アノテーションを使用しています。これもOpenCsvが提供するアノテーションです。

このアノテーションは、指定した column 値 と同値であるCSVヘッダー列を紐付けます。

つまり、今回の例で言えば [番号] というヘッダーをつけられたCSVの列を、データクラスの No フィールドに紐づける、ということを定義しています。

この利点は、データの紐付けがCSVのデータ順序に依存しないということです。たとえCSVが 名前>番号>場所 という順序で来ようと、 場所>番号>名前 という順序で来ようと、紐付けルールはヘッダー列を参照するので、常に正しい紐付けを行うことができます。

ではサービスの実装です。

みて分かったかもしれませんが、順序制御の時と全く同じ構成です。異なるのはクラスだけ、もう少し言うと、渡しているクラスが @CsvBindByPosition でフィールドを定義したものか、 @CsvBindByName でフィールド定義したものか、ということだけですね。

これで読み取れたデータは次の通りです。

順序ではなく、名称で紐付けできていることがわかりますね。

 

ちなみにサービスの実装ですが、順序制御と名前制御のメソッドにおいて使用するクラス情報以外に異なる点が無いとすれば、次のようにジェネリクスを使用して、汎用的な作りにしてしまった方が便利かもしれません。

 

使用したsrc

ここに置いておきます。

https://github.com/NanairoMegane/openCsv

まとめ

  • OpenCSVでは、CSVの読み書きに当たってコンマや改行文字に関する設定を行うことができる。
  • OpenCSVでは、CSVへ配列データを書き込むこともできるし、データクラスBeanを使用した書き込みも行うことができる。
  • OpenCSVでは、CSVの内容を文字配列のリストとして読み込むこともできるし、データクラスBeanに紐付けた上で、データクラスのリストとして読み込むこともできる。
  • CSVの内容をデータクラスのリストとして読み込む場合、CSVとデータクラスの紐付けルールは、順序、あるいはヘッダー名称を使用して設定することができる。

 

以上です。ここまで読んでくださり、ありがとうございました!

 

Kotlinカテゴリの最新記事