Skip to content

Reading Validation

nambach edited this page Nov 15, 2021 · 8 revisions

ExcelUtil provides built-in validation APIs that were inspired from Jakarta Bean Validation.

ReaderConfig<Book> readerConfig = ReaderConfig
    .fromClass(Book.class)
    .titleAtRow(1)
    .dataFromRow(2)
    // specify validation rules along with reading rules
    .column(0, "isbn", v -> v.isString().minLength(10, "ISBN must be at least 20 chars"))
    .column(1, "title", v -> v.notNull("Title must not be null"))
    .column(2, "author", v -> v.isString().notBlank("Must provide author"))
    .column(3, "rating", v -> v.isDecimal().notNull().between(0, 5, "Rating must be between 0 and 5"));

// Read data
Result<Book> books = readerConfig.readSheet(stream);

// Check errors
for (RowError row : books.getErrors()) {
    int atRow = row.getExcelIndex(); // row occurs error
    String message = row.getInlineMessage(); // summarized error message
    System.out.println("Row " + atRow + ": " + message);

    // object's detail errors
    ObjectError objectError = row.getObjectError();
    for (FieldError field : objectError) {
        String dtoField = field.getFieldName(); // field name
        List<String> dtoFieldErrors = field.getMessages(); // field's errors
    }
}

For example, with the below input, we will get these messages

Result

Row 3: 'title': Title must not be null
Row 4: 'isbn': Length must between 10 and 13; 'rating': must not be null
Row 5: 'isbn': Length must between 10 and 13; 'author': Must provide author; 'rating': Rating must be between 0 and 5

If you want to separate validation logic for further re-use, you can do as below.

// Declare independent validation object
Validator<Book> bookValidator = Validator
    .fromClass(Book.class)
    .on(f -> f.field("isbn")
              .validate(v -> v.isString().notNull()
                              .lengthBetween(10, 13, "Length must between 10 and 13")))
    .on(f -> f.field("title")
              .validate(v -> v.isString().notBlank("Title must be provided")))
    .on(f -> f.field("author")
              .validate(v -> v.isString().notBlank("Author must be provided")))
    .on(f -> f.field("rating")
              .validate(v -> v.isDecimal().notNull()
                              .between(0, 5, "Rating must be between 0 and 5")));


ReaderConfig<Book> readerConfig = ReaderConfig
    .fromClass(Book.class)
    .validator(bookValidator) // Then add it to the reader config
    ...

Manual validation

For more flexible validations, you can specify inside .handler() and .beforeAddingItem() as below

ReaderConfig<Book> readerConfig = ReaderConfig
    .fromClass(Book.class)
    ...
    .handler(set -> set.atColumn(3)
                       .handle((book, cell) -> {
                           Double rating = cell.readDouble();
                           if (rating == null) {
                               // set the error, and the row index with column title will be auto added
                               cell.setError("rating must not be null");
                               // or throw error and exit the reading process immediately
                               cell.throwError("rating must not be null");
                           }
                       }))
    .beforeAddingItem((book, row) -> {
        // you can get errors for the current read item
        List<RowError> errors = row.getErrors();

        // perform extra manual validation here...
        // skip item if it is not valid to add
        row.skipThisObject();

        // or stop immediately to save workload
        row.throwError("Stop due to...");
        // or simply this
        row.terminateNow();
    });