Java用のCSV (comma-separated values) パーサライブラリ。
CSV読み書きに関わる以下について実現可能。
- 1行に表示する値の数を任意に設定可能
- 引用符付きのエントリ内のコンマを無視
- キャリッジリターン(CR)が埋め込まれた引用符付きのエントリ(複数行にまたがるエントリなど)の処理
- セパレータ文字と引用符に任意のものを設定
CSVファイル書き込み
最小限のコードで実装
CSVの1レコードのデータを保持するクラスの作成
import lombok.Data; /** * サンプルマッピングクラス。<br> */ @Data public class Sample { /** 名前 */ private String name; /** 住所 */ private String address; }
CSVファイルに書き込む処理の実装
StatefulBeanToCsv#write()メソッドは、型違いで4つ存在するので、書き込むデータによって使い分けること。
- void write(T bean)
- void write(List
beans) - void write(Iterator
iBeans) - void write(Stream
beans)
/** * CSVファイルにデータを書き込むサンプル<br> */ public class SampleCsvWriter { public void write() { try (FileWriter writer = new FileWriter("sample.csv")) { Sample sample = new Sample(); sample.setName("太郎"); sample.setAddress("名古屋"); StatefulBeanToCsv<Sample> beanToCsv = new StatefulBeanToCsvBuilder<Sample>(writer) .build(); beanToCsv.write(sample); } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) { } } }
上記の実装で出力されるCSVファイルは以下の通り
"ADDRESS","NAME" "名古屋","太郎"
StatefulBeanToCsvBuilderの設定
区切り文字の設定
withSeparator()
で設定できる。デフォルトは「,
」
public class SampleCsvWriter { public void write() { try (FileWriter writer = new FileWriter("sample.csv")) { Sample sample = new Sample(); sample.setName("太郎"); sample.setAddress("名古屋"); StatefulBeanToCsv<Sample> beanToCsv = new StatefulBeanToCsvBuilder<Sample>(writer) .withSeparator(':') .build(); beanToCsv.write(sample); } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) { } } }
上記の実装で出力されるCSVファイルは以下の通り
"ADDRESS":"NAME" "名古屋":"太郎"
改行コードの設定
withLineEnd()
で設定できる。デフォルトは「\\n
」
public class SampleCsvWriter { public void write() { try (FileWriter writer = new FileWriter("sample.csv")) { Sample sample = new Sample(); sample.setName("太郎"); sample.setAddress("名古屋"); StatefulBeanToCsv<Sample> beanToCsv = new StatefulBeanToCsvBuilder<Sample>(writer) .withLineEnd("\\r\\n") .build(); beanToCsv.write(sample); } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) { } } }
ヘッダやレコードの囲み文字の設定
withQuotechar()
で設定できる。デフォルトは「"
」
public class SampleCsvWriter { public void write() { try (FileWriter writer = new FileWriter("sample.csv")) { Sample sample = new Sample(); sample.setName("太郎"); sample.setAddress("名古屋"); StatefulBeanToCsv<Sample> beanToCsv = new StatefulBeanToCsvBuilder<Sample>(writer) .withQuotechar('\\'') .build(); beanToCsv.write(sample); } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) { } } }
上記の実装で出力されるCSVファイルは以下の通り
'ADDRESS','NAME' '名古屋','太郎'
ヘッダやレコードを全て囲み文字で囲むかどうか
withApplyQuotesToAll()
で設定できる。デフォルトは「true
」
public class SampleCsvWriter { public void write() { try (FileWriter writer = new FileWriter("sample.csv")) { Sample sample = new Sample(); sample.setName("太郎"); sample.setAddress("名古屋"); StatefulBeanToCsv<Sample> beanToCsv = new StatefulBeanToCsvBuilder<Sample>(writer) .withApplyQuotesToAll(false) .build(); beanToCsv.write(sample); } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) { } } }
上記の実装で出力されるCSVファイルは以下の通り
ADDRESS,NAME 名古屋,太郎
エスケープ文字の設定
withEscapechar()
で設定できる。デフォルトは「"
」
public class SampleCsvWriter { public void write() { try (FileWriter writer = new FileWriter("sample.csv")) { Sample sample = new Sample(); sample.setName("太郎"); sample.setAddress("名古屋"); StatefulBeanToCsv<Sample> beanToCsv = new StatefulBeanToCsvBuilder<Sample>(writer) .withEscapechar('\\\\') .build(); beanToCsv.write(sample); } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) { } } }
上記の実装で出力されるCSVファイルは以下の通り
"ADDRESS","NAME" "\\"名古屋\\"","太郎"
カラムに付与可能なアノテーション
出力の確認には以下の処理を用いた。
public class SampleCsvWriter { public void write() { try (FileWriter writer = new FileWriter("sample.csv")) { Sample sample = new Sample(); sample.setName("太郎"); sample.setAddress("名古屋"); StatefulBeanToCsv<Sample> beanToCsv = new StatefulBeanToCsvBuilder<Sample>(writer) .build(); beanToCsv.write(sample); } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) { } } }
beanの項目のCSV出力の順番を定義する
@CsvBindByPosition
で指定可能。
@Data public class Sample { /** 名前 */ @CsvBindByPosition(position = 0) private String name; /** 住所 */ @CsvBindByPosition(position = 1) private String address; /** 年齢 */ @CsvBindByPosition(position = 2) private int age; }
ただし、このアノテーションを付けた状態でCSV出力しようとするとヘッダ行が表示されなくなる。
これは、デフォルトで設定されているMappingStrategyでは、CsvBindByNameとCsvBindByPositionを同時に使用することができないためである。
CsvBindByNameとCsvBindByPositionを同時に使用するためには、別途MappingStrategyを実装する必要がある。(後述あり)
出力されるCSVファイル:
"太郎","名古屋","10"
beanの項目のCSV出力の順番を定義する(コレクション用)
@CsvBindAndSplitByPosition
を用いて、リストなどのコレクションをCSV出力する場合のCSV出力の順番や、出力する際の要素間の区切り文字等を設定できる。
@Data public class Sample { /** 名前 */ @CsvBindByPosition(position = 0) private String name; /** 住所 */ @CsvBindByPosition(position = 1) private String address; /** 年齢 */ @CsvBindByPosition(position = 2) private int age; /** リスト */ @CsvBindAndSplitByPosition(elementType = String.class, position = 3, writeDelimiter = " ") private List<String> list; }
出力されるCSVファイル:
"太郎","名古屋","10","aiueo あいうえお"
ヘッダ名を定義する
@CsvBindByName
で指定可能。
ヘッダ名には日本語も設定可能。
ヘッダ名を指定すると動くcom.opencsv.bean.HeaderColumnNameMappingStrategy
の処理の中で、
アノテーションの属性に指定されたヘッダ名を大文字にしている処理があるため、英字のヘッダ名を指定すると全て大文字となる。
独自MappingStrategyを定義することで任意の英字をヘッダに指定可能。参考
@Data public class Sample { /** 名前 */ @CsvBindByName(column = "名前") private String name; /** 住所 */ @CsvBindByName(column = "address") private String address; /** 年齢 */ @CsvBindByName(column = "age") private int age; }
出力されるCSVファイル:
"ADDRESS","AGE","名前" "名古屋","10","太郎"
ヘッダ名を定義する(コレクション用)
@CsvBindAndSplitByName
を用いてリストなどのコレクションをCSV出力する場合のヘッダ名や、出力する際の要素間の区切り文字等を設定できる。
@Data public class Sample { /** 名前 */ @CsvBindByName(column = "名前") private String name; /** 住所 */ @CsvBindByName(column = "address") private String address; /** 年齢 */ @CsvBindByName(column = "age") private int age; /** リスト */ @CsvBindAndSplitByName(elementType = String.class, column = "list", writeDelimiter = " ") private List<String> list; }
出力されるCSVファイル:
"ADDRESS","AGE","LIST","名前" "名古屋","10","aiueo あいうえお","太郎"
日付のマッピング
日付フィールドに付与する。日付のフォーマットを指定可能。
@CsvDate
を付与するフィールドには@CsvBindByPosition
もしくは@CsvBindByName
も付与する必要がある。
@Data public class Sample { /** 名前 */ @CsvBindByPosition(position = 0) private String name; /** 住所 */ @CsvBindByPosition(position = 1) private String address; /** 年齢 */ @CsvBindByPosition(position = 2) private int age; /** リスト */ @CsvBindAndSplitByPosition(elementType = String.class, position = 3, writeDelimiter = " ") private List<String> list; /** 日時 */ @CsvDate("yyyy/MM/dd HH:mm:ss") @CsvBindByPosition(position = 4) private LocalDateTime datetime; }
出力されるCSVファイル:
"太郎","名古屋","10","aiueo あいうえお","2022/11/11 13:10:47"
数値のマッピング
数値を扱うラッパークラスやBigDecimal、BigIntegerなフィールドに付与する。
@CsvNumber
を付与するフィールドには@CsvBindByPosition
もしくは@CsvBindByName
も付与する必要がある。
そんなに使わない気がするアノテーションたち
CsvBindAndJoinByName, CsvBindAndJoinByPosition
汎用的に値をマッピングさせたいときに使用する。参考
「org.apache.commons.collections4.MultiValuedMap
」型のフィールドに付与する。
ヘッダ名を定義したい場合は「CsvBindAndJoinByName」で、 順番を定義したい場合は「CsvBindAndJoinByPosition」を使用する
利用シーンとしては、以下のBeanで複数のCSVファイルのマッピングに対応させたい場合っぽい・・・
@Data public class Person { /** ID */ @CsvBindByName(column = "id") String id; /** 名前 */ @CsvBindByName(column = "name") String name; /** 追加情報 */ @CsvBindAndJoinByName(column = ".*", elementType = String.class) Map<String,String> additionalInfo; }
"ID","NAME","ADDRESS" "1","山田","名古屋"
"ID","NAME","AGE" "1","山田","30"
CsvCustomBindByName, CsvCustomBindByPosition
CsvBindByNameやCsvBindByPositionと同じであるが、独自のデータ変換クラスを作成する必要がある。
CSVファイル読み込み
文字列配列で読み込む
public class SampleCsvReader { public void read() { try (FileReader reader = new FileReader("sample.csv")) { CSVReader csvReader = new CSVReaderBuilder(reader) .build(); // 全行まとめて読み込む List<String[]> list = csvReader.readAll(); } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) { fail(); } catch (CsvException e) { fail(); } } }
1行ずつ読み込むこともできる。
public class SampleCsvReader { public void read() { try (FileReader reader = new FileReader("sample.csv")) { CSVReader csvReader = new CSVReaderBuilder(reader) .build(); while ((nextLine = csvReader.readNext()) != null) { // 読み込んだ行に対する処理 } } catch (IOException | CsvException e) { fail(); } } }
beanで読み込む
JavaBeans形式でCSV読み込みする方法を見るに、 読み込みの時は全部Stringでないとマッピング出来なさそう・・・
任意のMappingStrategy実装
デフォルトの設定で気になる以下に対応するための独自クラスを作成する。
- ヘッダ名も指定したいし、CSVに出力する順番も定義したい
- ヘッダ名に小文字を設定したい
import org.apache.commons.lang3.StringUtils; import com.opencsv.bean.BeanField; import com.opencsv.bean.ColumnPositionMappingStrategy; import com.opencsv.bean.CsvBindByName; import com.opencsv.exceptions.CsvRequiredFieldEmptyException; public class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> { public CustomMappingStrategy(Class<? extends T> type) { setType(type); } @Override public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException { final int numColumns = getFieldMap().values().size(); super.generateHeader(bean); String[] header = new String[numColumns]; BeanField beanField; for (int i = 0; i < numColumns; i++) { beanField = findField(i); String columnHeaderName = extractHeaderName(beanField); header[i] = columnHeaderName; } return header; } private String extractHeaderName(final BeanField beanField) { if (beanField == null || beanField.getField() == null || beanField.getField().getDeclaredAnnotationsByType( CsvBindByName.class).length == 0) { return StringUtils.EMPTY; } final CsvBindByName bindByNameAnnotation = beanField.getField() .getDeclaredAnnotationsByType(CsvBindByName.class)[0]; return bindByNameAnnotation.column(); } }
作成したMappingStrategyクラスは、StatefulBeanToCsvBuilder#withMappingStrategy()
で設定する。
public class SampleCsvWriter { public void write() { try (FileWriter writer = new FileWriter("sample.csv")) { Sample sample = new Sample(); sample.setName("太郎"); sample.setAddress("名古屋"); CustomMappingStrategy<Sample> strategy = new CustomMappingStrategy<>(Sample.class); StatefulBeanToCsv<Sample> beanToCsv = new StatefulBeanToCsvBuilder<Sample>(writer) .withMappingStrategy(strategy) .build(); beanToCsv.write(sample); } catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) { } } }