Apache POI实现Excel转JSON 如何将输出数组改为普通列表
ExcelToJson 转换器
原实现代码
public JsonNode excelToJson(File excel) throws IOException { ObjectMapper mapper = new ObjectMapper(); // 按工作表维度存储Excel数据 ObjectNode excelData = mapper.createObjectNode(); FileInputStream fis = null; Workbook workbook = null; try { // 创建文件输入流 fis = new FileInputStream(excel); String filename = excel.getName().toLowerCase(); if (filename.endsWith(".xls") || filename.endsWith(".xlsx")) { // 根据Excel文件格式创建对应工作簿对象 if (filename.endsWith(".xls")) { workbook = new HSSFWorkbook(fis); } else { workbook = new XSSFWorkbook(fis); } // 逐工作表读取数据 for (int i = 0; i < workbook.getNumberOfSheets(); i++) { Sheet sheet = workbook.getSheetAt(i); String sheetName = sheet.getSheetName(); List<String> headers = new ArrayList<String>(); ArrayNode sheetData = mapper.createArrayNode(); // 逐行读取工作表内容 for (int j = 0; j <= sheet.getLastRowNum(); j++) { Row row = sheet.getRow(j); if (j == 0) { // 读取表头名称 for (int k = 0; k < row.getLastCellNum(); k++) { headers.add(row.getCell(k).getStringCellValue()); } } else { // 读取每行业务数据 ObjectNode rowData = mapper.createObjectNode(); for (int k = 0; k < headers.size(); k++) { Cell cell = row.getCell(k); String headerName = headers.get(k); if (cell != null) { switch (cell.getCellType()) { case FORMULA: rowData.put(headerName, cell.getCellFormula()); break; case BOOLEAN: rowData.put(headerName, cell.getBooleanCellValue()); break; case NUMERIC: rowData.put(headerName, cell.getNumericCellValue()); break; case BLANK: rowData.put(headerName, ""); break; default: rowData.put(headerName, cell.getStringCellValue()); break; } } else { rowData.put(headerName, ""); } } sheetData.add(rowData); } } excelData.set(sheetName, sheetData); } return excelData; } else { throw new IllegalArgumentException("不支持的文件格式。"); } } catch (Exception e) { e.printStackTrace(); } finally { if (workbook != null) { try { workbook.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; }
问题说明
上述方法可正常运行,但输出结果为嵌套对象包裹数组的结构:根节点是JSON对象,key为工作表名,value为对应工作表的行数据数组。后续将转换得到的数据写入数据库时,该结构会被ORM框架或数据库JSON解析器识别为单个复杂对象,无法映射为多行数据完成写入。
调整方案
去掉外层按工作表名封装的ObjectNode结构,直接返回平铺的行数据列表即可,调整后的实现返回的普通列表结构可以被所有持久层框架直接识别为多条待写入记录,不会被判定为单个对象。
单工作表场景(绝大多数业务导入适用)
直接读取第一个工作表的内容,返回行数据的顶层数组:
public ArrayNode excelToJson(File excel) throws IOException { ObjectMapper mapper = new ObjectMapper(); FileInputStream fis = null; Workbook workbook = null; ArrayNode resultList = mapper.createArrayNode(); try { fis = new FileInputStream(excel); String filename = excel.getName().toLowerCase(); if (!filename.endsWith(".xls") && !filename.endsWith(".xlsx")) { throw new IllegalArgumentException("不支持的文件格式。"); } // 创建工作簿 workbook = filename.endsWith(".xls") ? new HSSFWorkbook(fis) : new XSSFWorkbook(fis); // 仅读取第一个工作表,多工作表场景可自行修改遍历逻辑 Sheet sheet = workbook.getSheetAt(0); List<String> headers = new ArrayList<>(); // 逐行读取 for (int j = 0; j <= sheet.getLastRowNum(); j++) { Row row = sheet.getRow(j); if (j == 0) { // 读取表头 for (int k = 0; k < row.getLastCellNum(); k++) { headers.add(row.getCell(k).getStringCellValue()); } continue; } // 读取行数据 ObjectNode rowData = mapper.createObjectNode(); for (int k = 0; k < headers.size(); k++) { Cell cell = row.getCell(k); String headerName = headers.get(k); if (cell != null) { switch (cell.getCellType()) { case FORMULA: rowData.put(headerName, cell.getCellFormula()); break; case BOOLEAN: rowData.put(headerName, cell.getBooleanCellValue()); break; case NUMERIC: rowData.put(headerName, cell.getNumericCellValue()); break; case BLANK: rowData.put(headerName, ""); break; default: rowData.put(headerName, cell.getStringCellValue()); break; } } else { rowData.put(headerName, ""); } } resultList.add(rowData); } return resultList; } catch (Exception e) { e.printStackTrace(); } finally { // 流关闭逻辑保持不变 if (workbook != null) { try { workbook.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; }
多工作表场景
如果需要保留所有工作表的数据,给每条行数据增加sheetName字段标记来源,所有行统一放入顶层数组即可,不需要嵌套结构,核心修改逻辑如下:
for (int i = 0; i < workbook.getNumberOfSheets(); i++) { Sheet sheet = workbook.getSheetAt(i); String sheetName = sheet.getSheetName(); List<String> headers = new ArrayList<>(); for (int j = 0; j <= sheet.getLastRowNum(); j++) { Row row = sheet.getRow(j); if (j == 0) { headers.clear(); // 每个工作表重新读取表头 for (int k = 0; k < row.getLastCellNum(); k++) { headers.add(row.getCell(k).getStringCellValue()); } continue; } ObjectNode rowData = mapper.createObjectNode(); rowData.put("sheetName", sheetName); // 增加来源工作表标记 // 后续单元格读取逻辑和原实现一致,读取完成后add到resultList即可 resultList.add(rowData); } }
调整后返回的结构是顶层平铺的对象数组[{},{},{}...],持久层框架可以直接识别为多条记录,直接调用批量插入方法即可完成数据库写入,不会出现被识别为单个对象的问题。
如果需要返回Java原生
List结构而不是JsonNode,只需要把ArrayNode替换为List<Map<String,Object>>,每行数据用HashMap存储即可,序列化后结构和上述一致。
内容的提问来源于stack exchange,提问作者Alexander
相关产品推荐
相关产品推荐

