
本文详解如何使用 apache poi 读取 excel(.xlsx)文件,将多列中以逗号分隔的字符串(如“6-a,7-b,8-a”)智能拆解并结构化为符合预期的嵌套 json 数组,支持数值/字符串混合类型与字段语义映射。
本文详解如何使用 apache poi 读取 excel(.xlsx)文件,将多列中以逗号分隔的字符串(如“6-a,7-b,8-a”)智能拆解并结构化为符合预期的嵌套 json 数组,支持数值/字符串混合类型与字段语义映射。
在 Java 后端开发中,常需将 Excel 配置表(如教师排课表)转换为标准 JSON 结构供前端或微服务消费。当 Excel 单元格内存储多个逻辑项(如 class&Section 列值为 "6-A,7-B,8-A",Subject 列值为 "Tamil,English,Maths")时,直接逐单元格转 JSON 会导致扁平化数据,无法满足嵌套数组需求。本文提供一套健壮、可维护的解析方案,基于 Apache POI + org.json 库实现语义化转换。
✅ 核心设计思路
- 表头驱动映射:动态读取首行作为 JSON 字段名(如 "Teacher_code", "class&Section", "Subject_name"),避免硬编码;
-
智能字段识别:对不同列采用差异化解析策略:
- Teacher_code:纯数字但需保留原始字符串形式(防止科学计数法如 2.34566E7);
- class&Section:按 - 拆分生成 { "class": "6", "section": "A" } 对象;
- Subject_name:按 , 拆分生成 { "subject": "Tamil" } 对象;
- 类型安全处理:显式区分 CELL_TYPE_STRING 和 CELL_TYPE_NUMERIC,对数字型单元格使用 NumberFormat 避免精度丢失;
- 资源自动管理:使用 try-with-resources 确保 InputStream 和 Workbook 正确关闭。
? 完整实现代码
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.NumberFormat;
import java.util.*;
import java.util.stream.Collectors;
public class ExcelToNestedJson {
public static void main(String[] args) throws Exception {
String excelPath = "C:/Users/HP/Downloads/school.xlsx";
try (InputStream in = Files.newInputStream(Paths.get(excelPath));
Workbook workbook = new XSSFWorkbook(in)) {
Sheet sheet = workbook.getSheetAt(0);
Row headerRow = sheet.getRow(sheet.getFirstRowNum());
// 提取表头(列名),作为 JSON key
List<String> headers = new ArrayList<>();
for (int j = headerRow.getFirstCellNum(); j < headerRow.getLastCellNum(); j++) {
Cell cell = headerRow.getCell(j);
headers.add(cell == null ? "" : cell.getStringCellValue().trim());
}
List<JSONObject> resultArray = new ArrayList<>();
// 从第二行开始遍历数据行(跳过表头)
for (int i = sheet.getFirstRowNum() + 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
JSONObject teacherObj = new JSONObject();
for (int j = row.getFirstCellNum(); j < row.getLastCellNum(); j++) {
Cell cell = row.getCell(j);
if (cell == null) continue;
String header = headers.get(j);
String valueStr = "";
// 统一提取字符串值(兼容数字和文本)
switch (cell.getCellType()) {
case STRING:
valueStr = cell.getStringCellValue().trim();
break;
case NUMERIC:
// 防止科学计数法,转为无格式字符串
NumberFormat nf = NumberFormat.getInstance();
nf.setGroupingUsed(false);
valueStr = nf.format(cell.getNumericCellValue()).trim();
break;
default:
continue;
}
// 根据列名执行语义化解析
if ("Teacher_code".equalsIgnoreCase(header)) {
teacherObj.put("Teacher_code", valueStr);
} else if ("class&Section".equalsIgnoreCase(header)) {
JSONArray classArray = new JSONArray();
for (String item : valueStr.split("\s*,\s*")) {
if (item.isEmpty()) continue;
String[] parts = item.split("-", 2); // 最多切两段:class-section
JSONObject classObj = new JSONObject();
classObj.put("class", parts[0].trim());
if (parts.length > 1) {
classObj.put("section", parts[1].trim());
} else {
classObj.put("section", ""); // 兼容无 section 场景
}
classArray.put(classObj);
}
teacherObj.put("class", classArray); // 注意:目标字段名为 "class"(非 "class&Section")
} else if ("Subject_name".equalsIgnoreCase(header)) {
JSONArray subjectArray = new JSONArray();
for (String subject : valueStr.split("\s*,\s*")) {
if (subject.isEmpty()) continue;
JSONObject subjObj = new JSONObject();
subjObj.put("subject", subject.trim());
subjectArray.put(subjObj);
}
teacherObj.put("subject_name", subjectArray);
}
}
resultArray.add(teacherObj);
}
// 输出最终嵌套 JSON(格式化可选)
System.out.println(new JSONArray(resultArray).toString(2)); // 缩进输出便于调试
}
}
}⚠️ 关键注意事项
-
依赖配置:确保 pom.xml 中包含:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20231013</version> </dependency> - 空单元格与空白处理:代码中使用 \s*,\s* 正则分割,自动忽略逗号前后空格;对空 Cell 或空字符串做防御性跳过;
- 字段名映射灵活性:示例中将 Excel 列 "class&Section" 映射为 JSON 字段 "class",可根据实际 API 规范调整 put() 的 key 名;
- 性能优化建议:对于超大 Excel(>10MB),可考虑使用 SXSSFWorkbook 流式读取,避免内存溢出;
- 错误处理增强:生产环境应捕获 IllegalArgumentException(如非法 - 分割)、JSONException 并记录详细上下文(行号、列名)。
✅ 总结
本方案摒弃了原始代码中将整行压入扁平 JSONArray 的做法,转而以列语义为中心构建嵌套结构,既满足了多值单元格的深度解析需求,又保持了代码的清晰性与可扩展性。通过表头驱动 + 类型感知 + 正则健壮分割,可稳定支撑教育、排班、配置管理等典型业务场景的数据导入任务。










