解析EXCEL文件的通用方法

人气:388 发布:2022-10-16 标签: java excel apache-poi spring spring-boot

问题描述

我需要解析EXCEL文件并从中创建对象列表。为了做同样的事情,我们正在使用There Aresorg.apache.poi读取EXCEL文件,我们能够获得所需的详细信息,目前我们正在根据索引获取单元格值,并将其设置为对象字段。但我们认为这不是基于索引获取值的好方法,我们应该找到一种通用的方法来成功解析EXCEL文件,以防添加或删除一些列,这样我们就不必在代码上做太多工作。我遇到了this article,它几乎满足了要求,但使用了我们不允许使用的反射方法。有没有可能在不使用单元格索引的情况下解析EXCEL文件,这样如果EXCEL文件的格式发生更改,我们就不需要花费太多精力?

public List<DTO> jsonConverter(Workbook workbook, Sheet sheet, String filename)
      throws ParseException {
    List<DTO> listOfDTOs = new ArrayList<>();
    Row row;
    for (int index = 1; index <= sheet.getLastRowNum(); index++) {
      row = sheet.getRow(index);
      if (row != null) {
        DTO dto = new DTO();
        dto.setFieldX(
            getCellValueAsStringBasedOnCellType(
                workbook, row.getCell(0, MissingCellPolicy.CREATE_NULL_AS_BLANK)));
        dto.setFieldY(
            getCellValueAsStringBasedOnCellType(
                workbook, row.getCell(1, MissingCellPolicy.CREATE_NULL_AS_BLANK)));
        listOfDTOs.add(dto);
      }
    }
    return listOfDTOs;
  }

public String getCellValueAsStringBasedOnCellType(Workbook workbook, Cell cell) {
    DataFormatter formatter = new DataFormatter();
    if (cell != null && cell.getCellType() == CellType.FORMULA) {
      FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
      return formatter.formatCellValue(cell, evaluator);
    }
    return formatter.formatCellValue(cell);
  }

推荐答案

当然。很简单:

安装

读取Excel文件中的第一行,将其视为列,并将每个单元格视为字符串。

将这些存储在数组中。

现在,您只需使用表达式headers[idx]即可将任何索引转换为列名。

因此,对于任何给定的单元格,您都知道标题名称。现在,您需要将这些知识转化为正确的预测。假设您在列E(i == 4)中,头文件的值是header[4],也就是Address,然后您希望获取字符串("Address")并将其转换为正确的调用。您最终需要调用:

String cellValue = getCellValueAsStringBasedOnCellType(workbook, row.getCell(1, MissingCellPolicy.CREATE_NULL_AS_BLANK)));

dto.setFieldAddress(cellValue);

该代码片段中的所有内容对于该字符串的任何值都是相同的,setFieldAddress名称除外。

因此,我们需要将字符串"Address"转换为调用setFieldAddress的操作。

解决方案

java.util.function和用于救援的哈希图!

这是一种将获取dto实例并设置地址字段的概念存储在变量中的方法:

BiConsumer<DTO, String> setAddress = (dto, value) -> dto.setFieldAddress(value);

甚至更简单:

BiConsumer<DTO, String> setAddress = DTO::setFieldAddress;

这些代码片段做的是相同的事情:它们不设置地址;它们是如何设置地址的秘诀,并且您将在DTO上设置地址的概念存储在一个变量中,以便您可以在以后运行它,并且可以想运行多少次就运行多少次。这通常称为"闭包"或"lambda"。

我们可以将这些内容存储在地图中:

Map<String, BiConsumer<DTO, String>> dtoSetters = new HashMap<>();
dtoSetters.put("Address", DTO::setFieldAddress);

然后我们就可以搞清楚了:

int colIdx = ...;
String headerName = header[colIdx];
var setter = dtoSetters.get(headerName);
if (setter == null) throw new IllegalStateException("Unexpected column header in excel sheet: " + headerName);

String cellValue = getCellValueAsStringBasedOnCellType(workbook, row.getCell(1, MissingCellPolicy.CREATE_NULL_AS_BLANK)));

setter.apply(dto, cellValue);

因此,创建该映射(一旦,在系统引导时,例如使用静态初始值设定项),用上面的代码替换您的dto.setFieldX代码,就可以了。

953