本文详解如何使用 apache poi 读取 excel(.xlsx)文件,针对含逗号分隔值的单元格(如“6-a,7-b,8-a”或“tamil,english,maths”),自动解析并构造成结构化 jsonarray,支持多层嵌套对象(如 class-section 对、subject 列表)。
本文详解如何使用 apache poi 读取 excel(.xlsx)文件,针对含逗号分隔值的单元格(如“6-a,7-b,8-a”或“tamil,english,maths”),自动解析并构造成结构化 jsonarray,支持多层嵌套对象(如 class-section 对、subject 列表)。
在 Java 后端开发中,常需将业务人员提供的 Excel 配置表(如教师-班级-科目映射表)转换为标准 JSON 格式供 API 或前端消费。当 Excel 单元格内存储多个逻辑项(如 class&Section 列值为 "6-A,7-B,8-A",Subject 列值为 "Tamil,English,Maths")时,直接逐单元格转 JSON 会导致扁平化结构,无法满足嵌套数组需求。本教程提供一套健壮、可维护、符合 RESTful 数据规范的解决方案,基于 Apache POI + org.json(或 Alibaba FastJSON),无需引入复杂 ORM,纯内存解析。
✅ 核心设计原则
- 表头驱动:动态读取首行作为 JSON 字段名(如 "Teacher_code", "class&Section", "Subject"),提升配置灵活性;
- 类型智能识别:区分 STRING 与 NUMERIC 单元格,避免科学计数法(如 2.34566E7)误解析;
-
语义化拆分:对含分隔符字段按业务规则解析——
- 若含 "-"(如 "6-A")→ 拆为 { "class": "6", "section": "A" };
- 若无 "-"(如 "Tamil")→ 视为独立 subject → { "subject": "Tamil" };
- 资源安全释放:使用 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.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
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 {
Path excelPath = Paths.get("school.xlsx"); // 替换为实际路径
try (InputStream in = Files.newInputStream(excelPath);
Workbook workbook = new XSSFWorkbook(in)) {
Sheet sheet = workbook.getSheetAt(0);
if (sheet == null) throw new IllegalArgumentException("Sheet is empty or invalid");
// ✅ 步骤1:读取表头(首行),作为 JSON 键名
Row headerRow = sheet.getRow(sheet.getFirstRowNum());
List<String> headers = new ArrayList<>();
for (int j = headerRow.getFirstCellNum(); j < headerRow.getLastCellNum(); j++) {
Cell cell = headerRow.getCell(j);
headers.add(cell != null && cell.getCellType() == CellType.STRING
? cell.getStringCellValue().trim()
: "column_" + j);
}
// ✅ 步骤2:遍历数据行(跳过首行)
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();
// ✅ 步骤3:逐列解析,按表头名和单元格内容生成嵌套结构
for (int j = row.getFirstCellNum(); j < row.getLastCellNum(); j++) {
Cell cell = row.getCell(j);
if (cell == null) continue;
String header = headers.get(j);
JSONArray jsonArray = new JSONArray();
switch (cell.getCellType()) {
case STRING:
String rawValue = cell.getStringCellValue().trim();
if (!rawValue.isEmpty()) {
String[] values = rawValue.split("\s*,\s*"); // 健壮分割:忽略逗号前后空格
for (String v : values) {
v = v.trim();
JSONObject item = new JSONObject();
if (v.contains("-")) { // 处理 "class-section" 类型(如 "6-A")
String[] parts = v.split("-", 2);
item.put("class", parts[0]);
item.put("section", parts.length > 1 ? parts[1] : "");
} else if (header.toLowerCase().contains("subject")) { // 主题类字段
item.put("subject", v);
} else { // 其他字符串字段(如教师编码)
item.put(header, v);
}
jsonArray.put(item);
}
}
teacherObj.put(header, jsonArray);
break;
case NUMERIC:
// 防止科学计数法,统一转为字符串(适用于教师编号等非数值计算场景)
NumberFormat nf = NumberFormat.getInstance(Locale.US);
nf.setGroupingUsed(false);
String numStr = nf.format(cell.getNumericCellValue());
// 教师编码列通常为数字但需保留前导零 → 存为字符串
if (j == 0) {
teacherObj.put(header, numStr);
} else {
// 其他数值列若需作为 class 单独处理(无 section),可扩展此处逻辑
JSONObject singleClass = new JSONObject();
singleClass.put("class", numStr);
jsonArray.put(singleClass);
teacherObj.put(header, jsonArray);
}
break;
default:
teacherObj.put(header, "");
}
}
resultArray.add(teacherObj);
}
// ✅ 输出最终 JSON 数组(格式化缩进便于调试)
System.out.println(new JSONObject().put("data", new JSONArray(resultArray)).toString(2));
} catch (IOException e) {
throw new RuntimeException("Failed to read Excel file", e);
}
}
}⚠️ 关键注意事项
-
依赖配置:确保 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> - 空单元格/空行处理:代码中已加入 if (row == null) continue 和 if (cell == null) continue,避免 NPE;
- 分隔符鲁棒性:使用正则 \s*,\s* 分割,兼容 "6-A, 7-B ,8-A" 等含空格格式;
- 字段映射灵活性:通过 header.toLowerCase().contains("subject") 判断主题列,可按需改为精确匹配(如 header.equals("Subject"));
- 性能提示:对于超大 Excel(>10MB),建议改用 SXSSFWorkbook 流式读取,避免 OOM。
✅ 验证输出示例
输入 Excel 行:
| Teacher_code | class&Section | Subject |
|--------------|----------------|-------------------|
| 23424234 | 6-A,7-B,8-A | Tamil,English,Maths |
输出 JSON 片段:
{
"data": [
{
"Teacher_code": "23424234",
"class&Section": [
{ "class": "6", "section": "A" },
{ "class": "7", "section": "B" },
{ "class": "8", "section": "A" }
],
"Subject": [
{ "subject": "Tamil" },
{ "subject": "English" },
{ "subject": "Maths" }
]
}
]
}该方案兼顾可读性、可维护性与生产健壮性,可直接集成至 Spring Boot Controller 或定时任务中,成为 Excel 配置驱动服务的标准解析模块。










