Bean単位での値の検証方法

CellProcessorは、フィールド1つに対する単項目チェックです。

項目間に対する相関チェックを行う場合は、CsvValidator で1レコード分のBeanに対するチェック処理を実装します。

CsvValidatorの実装

CsvValidator のメソッド validate(...) を実装します。

  • エラー情報は、CsvBindingErrors に格納し管理されています。

    • フィールドに対してエラーメッセージを追加する場合は、CsvBindingErrors#rejectValue(...) で追加します。

    • 項目間などのグローバルなエラーメッセージを追加する場合は、CsvBindingErrors#reject(...) で追加します。

  • CsvValidatorは、CellProcessorの後に実行され、もしエラーがある場合、そのフィールドの値は空が設定されており、 CsvBindingErrors#getFieldErrors(<フィールド名>) でフィールドに対するエラーがあるかどうか判定する必要があります。

    • 既にエラーがある場合などの判定処理を巻単位するため、CsvField クラスを利用します。

CsvValidatorの実装例
 1import java.util.Map;
 2
 3import com.github.mygreen.supercsv.validation.CsvBindingErrors;
 4import com.github.mygreen.supercsv.validation.CsvField;
 5import com.github.mygreen.supercsv.validation.CsvFieldValidator;
 6import com.github.mygreen.supercsv.validation.CsvValidator;
 7import com.github.mygreen.supercsv.validation.ValidationContext;
 8
 9// SampleCsvに対するValidator
10public class SampleValidator implements CsvValidator<SampleCsv> {
11
12    @Override
13    public void validate(final SampleCsv record, final CsvBindingErrors bindingErrors,
14            final ValidationContext<SampleCsv> validationContext) {
15
16        // フィールド ageの定義
17        final CsvField<Integer> ageField = new CsvField<>(bindingErrors, validationContext, record, "age");
18
19        // フィールド salaryの定義
20        final CsvField<Integer> salaryField = new CsvField<>(bindingErrors, validationContext, record, "salary");
21        salaryField
22            .add(new CsvFieldValidator<Integer>() {
23
24                @Override
25                public void validate(final CsvBindingErrors bindingErrors, final CsvField<Integer> field) {
26                    if(ageField.isEmpty()) {
27                        return;
28                    }
29
30                    // カラム「age(年齢)」が20以上の場合、カラム「給料(salary)」が設定されているかチェックする。
31                    if(ageField.isNotEmpty() && ageField.getValue() >= 20 && field.isEmpty()) {
32                        // メッセージ中の変数の作成
33                        final Map<String, Object> vars = createMessageVariables(field);
34                        vars.put("maxAge", 20);
35
36                        // ageに関するフィールドエラーの追加
37                        bindingErrors.rejectValue(field.getName(), field.getType(), "age.required", vars);
38                    }
39                }
40            })
41            .add(new MaxValidator(10_000_000))
42            .validate(bindingErrors);
43
44
45    }
46
47    // CsvFieldValidator を別に実装
48    public static class MaxValidator implements CsvFieldValidator<Integer> {
49
50        private final int max;
51
52        public MaxValidator(final int max) {
53            this.max = max;
54        }
55
56        @Override
57        public void validate(CsvBindingErrors bindingErrors, CsvField<Integer> field) {
58            if(field.isEmpty()) {
59                return;
60            }
61
62            if(field.getValue() > max) {
63                // メッセージ変数の組み立て
64                Map<String, Object> vars = createMessageVariables(field);
65                vars.put("max", max);
66
67                bindingErrors.rejectValue(field.getName(), field.getType(), "fieldError.max", vars);
68            }
69        }
70
71    }
72
73}

CsvFieldValidator#createMessageVariables(...) を利用すると、以下の良く使用するCSV情報に関する変数を作ることができます。

よく使用するな変数

変数名

説明

lineNumber

CSVの実ファイル上の行番号。
カラムの値に改行が含まれている場合を考慮した実際の行番号なります。
1から始まります。

rowNumber

CSVの論理上の行番号です。
1から始まります。

columnNumber

CSVの列番号です。
1から始まります。

label

@CsvColumn(label="<見出し>") 出指定したカラムの見出し名です。
label属性を指定していない場合は、フィールド名になります。

validatedValue

不正となった値です。

printer

各フィールドの TextFormatter のインスタンスです。
${print#print(validatedValue)} でパース済みのオブジェクトをフォーマットするのに利用します。

作成したCsvValidatorは、 @CsvBean(validators=) で指定します。

Beanの実装例
 1import com.github.mygreen.supercsv.annotation.CsvBean;
 2import com.github.mygreen.supercsv.annotation.CsvColumn;
 3import com.github.mygreen.supercsv.annotation.constraint.CsvNumberMin;
 4import com.github.mygreen.supercsv.annotation.constraint.CsvRequire;
 5
 6
 7@CsvBean(header=true, validators=SampleValidator.class)
 8public class SampleCsv {
 9
10    @CsvColumn(number=1, label="名前")
11    @CsvRequire(considerBlank=true)
12    private String name;
13
14    @CsvColumn(number=2, label="年齢")
15    private Integer age;
16
17    @CsvColumn(number=3, label="給料")
18    @CsvNumberMin("0")
19    private Integer salary;
20
21    // setter/ getterは省略
22
23}

エラーメッセージの定義

CsvBindingErrors#reject(...)/rejectValue(...) でエラーメッセージを追加する場合、エラーコードを指定します。

  • エラーメッセージは、クラスパスのルートに配置したプロパティファイル SuperCsvMessages.properties が自動的に読み込まれるため、そこに定義します。

  • プロパティファイルは、UTF-8、ASCIIコード変換なしで作成します。 [ver.2.2]

  • メッセージキーは、「エラーコード」「クラス名」「フィールド名」「フィールドのクラスタイプ」を組み合わせて、優先順位の高いものに一致した物が採用されます。

  • メッセージ中では変数が利用可能で、予め利用可能な変数は下記が登録されています。

    • メッセージ変数は、{key} で参照可能です。

  • さらに、${exp} の形式だと、式言語として JEXL が利用可能です。

  • ただし、 Spring Frameworkと連携してエラーメッセージの取得方法を変更 している場合は、定義する箇所は異なります。

メッセージキーの候補

優先度

形式

1

<エラーコード>.<Beanのクラス名>.<フィールド名>

age.reqired.SampleCsv.age
※「age.required」がエラーコード

2

<エラーコード>.<フィールド名>

age.reqired.age
※「age.required」がエラーコード

3

<エラーコード>.<フィールドのクラスパス>

age.reqired.java.lang.Integer
※「age.required」がエラーコード

4

<エラーコード>.<フィールドのクラスタイプの親のクラスパス>
※数値型と列挙型のみ
age.reqired.java.lang.Number
age.reqired.java.lang.Enum
※「age.required」がエラーコード

5

<エラーコード>

age.reqired
※「age.required」がエラーコード
SuperCsvMessage.propertiesの定義例
 1###################################################
 2# 独自のエラーメッセージの定義
 3###################################################
 4# 定義したキーは、再帰的に{キー名}で参照可能
 5
 6csvContext=[{rowNumber}行, {columnNumber}列]
 7
 8## エラーコードに対するメッセージの定義
 9age.required={csvContext} : 項目「{label}」は、年齢が{maxAge}歳以上の場合には必須です。
10fieldError.max={csvContext} : 項目「{label}」の値(${printer.print(validatedValue)})は、${printer.print(max)}以内で入力してください。

エラーのハンドリング

CsvValidatorの中でCsvBindingErrorsにエラー情報を追加すると、例外 SuperCsvBindingException としてスローされます。

  • 例外クラスは、 CsvExceptionConverter メッセージに変換します。

  • CsvExceptionConverter は、CsvAnnotationBeanReader/CsvAnnotationBeanWriter に組み込まれているため、 メソッド #getErrorMessages() で取得できます。

 1import java.nio.charset.Charset;
 2import java.nio.file.Files;
 3import java.io.File;
 4import java.util.ArrayList;
 5import java.util.List;
 6
 7import org.supercsv.prefs.CsvPreference;
 8import org.supercsv.exception.SuperCsvException;
 9
10import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
11import com.github.mygreen.supercsv.validation.CsvExceptionConverter;
12
13
14public class Sample {
15
16    public void sampleRead() {
17
18        CsvAnnotationBeanReader<SampleCsv> csvReader;
19        try {
20            csvReader = new CsvAnnotationBeanReader<>(
21                SampleCsv.class,
22                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
23                CsvPreference.STANDARD_PREFERENCE);
24
25            // ファイルの読み込み
26            List<SampleCsv> list = csvReader.readAll();
27
28        } catch(SuperCsvException e) {
29
30            // 変換されたエラーメッセージの取得
31            List<String> messages = csvReader.getErrorMessages();
32
33        } finally {
34            if(csvReader != null) {
35                csvReader.close();
36            }
37        }
38    }
39
40}