写一个功能,任意json生成excel,每个数组都单独生成一个sheet。
参考资料:Apache POI 使用教程
主要实现思路: 使用支持Java对象与JSON对象、字符串互相转换的fastjson,以及支持Java将JSON转化Excel的库 apache-poi
Excel表格关键结构:
JSON转换的几种情形与实现思路:
情形一:普通的单层结构,多个JSON对象
{
"班级A" : [{
"文章":"课文1",
"作者":"李白"
},
{
"文章":"课文2",
"作者":"小李"
},
{
"文章":"课文2",
"作者": "小明"
}]
}
导出结果:
当我们使用fastjson遍历JSONObject时,每次读取到的都是单个{ } 所包含的对象,比如:
{
"文章":"课文1",
"作者":"李白"
}
这种情况下,我们在Excel的Sheet中的行是确定的,比如这里就是第二行(第一行是列名),行根据遍历的顺序确定,而列则是不确定的,在这里有 “文章”,“作者” 这两个列,但是一开始这两个列是不存在的。这里则确定文章在第一列,作者按第二列(默认升序排序)。
当遍历下一个对象时,我们可能遇到旧的列,也可能遇到新的列,比如:
{
"文章":"课文2",
"作者":"李白",
"出版日期": "2022年7月6日"
}
这时,我们需要知道"文章" 和 “作者” 在第几列,同时也要知道 “出版日期” 应该在第几列,否则就不能确定唯一的单元格,将 JSON的value存储进去。
这里可以使用 Map
来记录列名以及下标。
在遍历对象时,key是列名,value则是单元格该填的值,如果 map.get(key) 的结果是空的,说明该列不存在,则需要创建,如果存在,那么可以创建单元格的对象,将值填入即可。
情形二:嵌套结构,JSON数组的嵌套
{
"班级A":[
{
"学号":"A01",
"语文":[
{
"文章":"课文1",
"作者":"李白"
},
{
"文章":"课文2",
"作者":"小李"
},
{
"文章":"课文2",
"作者": "小明"
}
],
"数学":"130"
},
{
"学号":"A02",
"语文":"130",
"数学":"135"
}
],
}
实现效果:
这里相比之前的情况复杂了一些,主要的就是需要再次创建一个新的 sheet,这意味着需要使用递归完成创建,于是我们可以将之前那种情形的代码实现封装成一个方法,比如createSubSheet(),在遍历JSON对象时,如果value值是一个JSONAarray,那么就再次调用createSubSheet()这个方法,只要使用同一个Workbook对象,表示同一个excel文件,就能满足这个需求了。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>groupIdgroupId>
<artifactId>poi_demoartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>5.2.2version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.9version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>2.0.7version>
dependency>
dependencies>
project>
JSONToExcelUtil.java
package cn.uni;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.schema.JSONSchema;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* uni
* 2022/07/05~2022/07/06
* 将 JSON 转化为 Excel的工具类
*/
public class JSONToExcelUtil {
/**
* 读取绝对路径下的json文件
* @param resourcePath json文件的绝对路径
* @return json文件格式化后的字符串
*/
public static String readJSONFile(String resourcePath) {
try{
// 1. 创建文件流
File file = new File(resourcePath);
// 2. 使用 common-lang3工具包, 以 UTF-8 格式读取文件, 转为字符串
String str = FileUtils.readFileToString(file, "UTF-8");
JSONObject jsonObject = JSONObject.parseObject(str);
// 3. 将字符串转为标准的JSON格式的字符串
return JSONObject.toJSONString(jsonObject, JSONWriter.Feature.WriteMapNullValue);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 创建 Sheet
* @param layer 当前Sheet所在JSON中的层级
* @param workbook 工作台 ( excel表格的主体 )
* @param sheetName 当前页的名称
* @param jsonArray JSON数组
*/
public static void createSubSheet(int layer, XSSFWorkbook workbook, String sheetName, JSONArray jsonArray){
// 创建新的 sheet
XSSFSheet sheet = workbook.createSheet(sheetName);
// 存储每个字段
Map<String, Integer> map = new HashMap<>();
// 统计当前的列
int cellCount = 0;
// 创建第一行
XSSFRow firstRow = sheet.createRow(0);
// 获取每一项
for (int row = 1; row <= jsonArray.size(); row++) {
JSONObject jsonObject = jsonArray.getJSONObject(row - 1);
// 创建行
XSSFRow currentRow = sheet.createRow(row);
if(jsonObject != null){
// 遍历每个KV
for (String cellName : jsonObject.keySet()) {
// 列不存在时, 则创建列
if (!map.containsKey(cellName)) {
// 第一行创建列
XSSFCell firstRowCell = firstRow.createCell(cellCount);
firstRowCell.setCellValue(cellName);
map.put(cellName, cellCount++);
}
// 设置单元格
XSSFCell cell = currentRow.createCell(map.get(cellName));
// 获取 Value
String cellValue = JSON.toJSONString(jsonObject.get(cellName));
// 如果V为数组则递归创建sheet
if(JSON.isValidArray(cellValue)){
String subCellName = sheetName + "-" + cellName;
cell.setCellValue(subCellName);
createSubSheet(layer + 1, workbook,subCellName, jsonObject.getJSONArray(cellName));
}
else{
cell.setCellValue(jsonObject.getString(cellName));
}
}
} else{ // Value为一个数组
JSONArray array = jsonArray.getJSONArray(row - 1);
// 遍历数组
if(array != null && array.size() > 0){
for (int i = 1; i <= array.size(); i++) {
JSONObject obj = array.getJSONObject(i - 1);
// 遍历 obj
for (String cellName : obj.keySet()) {
// 若列不存在则添加
if(!map.containsKey(cellName)){
XSSFCell cell = firstRow.createCell(cellCount);
map.put(cellName, cellCount++);
cell.setCellValue(cellName);
}
// 分情况讨论
String cellValue = obj.getString(cellName);
XSSFCell cell = currentRow.createCell(map.get(cellName));
// 如果值是JSON对象, 则递归创建
if(JSON.isValidObject(cellValue)){
String subSheetName = sheetName + "-" + cellName;
cell.setCellValue(subSheetName);
createSubSheet(layer+1, workbook, subSheetName , JSONObject.parseObject(cellValue));
} else if(JSON.isValidArray(cellValue)){
String subSheetName = sheetName + "-" + cellName;
cell.setCellValue(subSheetName);
createSubSheet(layer+1, workbook, subSheetName , JSONArray.parseArray(cellValue));
} else {
cell.setCellValue(cellValue);
}
}
}
} else {
firstRow.createCell(0).setCellValue(sheetName);
XSSFCell cell = currentRow.createCell(cellCount);
cell.setCellValue(jsonArray.getString(row-1));
}
}
}
}
/**
* 创建 Sheet
* @param layer 当前Sheet所在JSON中的层级
* @param workbook 工作台 ( excel表格的主体 )
* @param sheetName 当前页的名称
* @param jsonObject JSON对象
*/
public static void createSubSheet(int layer, XSSFWorkbook workbook, String sheetName, JSONObject jsonObject){
// 创建新的 sheet
XSSFSheet sheet = workbook.createSheet(sheetName);
// 存储每个字段
Map<String, Integer> map = new HashMap<>();
// 统计当前的列
int cellCount = 0;
// 创建第一行
XSSFRow fistRow = sheet.createRow(0);
// 记录行数
int row = 1;
// 获取每一项
// 创建行
XSSFRow currentRow = sheet.createRow(row);
// 遍历每个KV
for (String cellName : jsonObject.keySet()) {
// 列不存在时, 则创建列
if (!map.containsKey(cellName)) {
// 第一行创建列
XSSFCell firstRowCell = fistRow.createCell(cellCount);
firstRowCell.setCellValue(cellName);
map.put(cellName, cellCount++);
}
// 设置单元格
XSSFCell cell = currentRow.createCell(map.get(cellName));
// 获取 Value
String cellValue = JSON.toJSONString(jsonObject.get(cellName));
// 如果V为对象则递归创建sheet
if(JSON.isValidObject(cellValue)){
String subCellName = "Sheet" + layer + "-" + sheetName + "-" + cellName;
cell.setCellValue(subCellName);
createSubSheet(layer + 1, workbook,subCellName, JSON.parseObject(cellValue));
} else if(JSON.isValidArray(cellValue)){
String subCellName = "Sheet" + layer + "-" + sheetName + "-" + cellName;
cell.setCellValue(subCellName);
createSubSheet(layer + 1, workbook,subCellName, JSON.parseArray(cellValue));
}
else{
cell.setCellValue(jsonObject.getString(cellName));
}
}
}
/**
* 将格式化的JSON字符串导出为Excel
* @param jsonStr 格式化后的JSON字符串
* @param savePath Excel保存路径
* @param excelName Excel名称
*/
public static void toExcelByString(String jsonStr, String savePath, String excelName){
assert JSON.isValid(jsonStr) : "字符串: " + jsonStr + " 不是标准的JSON字符串";
toExcelByJSONObject(JSONObject.parseObject(jsonStr),savePath, excelName);
}
/**
* 将普通的Java对象导出为JSON文件
* @param obj Java对象
* @param savePath Excel保存路径
* @param excelName Excel名称
*/
public static void toExcelByObject(Object obj, String savePath, String excelName){
String jsonStr = JSON.toJSONString(obj, JSONWriter.Feature.WriteMapNullValue);
JSONObject jsonObject = JSONObject.parseObject(jsonStr);
toExcelByJSONObject(jsonObject, savePath, excelName);
}
/**
* 将本地的JSON文件导出为 Excel
* @param resourcePath JSON文件的绝对路径
* @param savePath 保存的路径
* @param excelName 保存的Excel名称
*/
public static void toExcelByLocalJSONFile(String resourcePath, String savePath, String excelName){
// 1. 获取标准的 JSON 字符串
String jsonStr = readJSONFile(resourcePath);
// 验证字符串是否合法
assert JSON.isValid(jsonStr) : "路径:[" + resourcePath + "] 的json文件不符合标准的JSON格式";
toExcelByString(jsonStr, savePath, excelName);
}
/**
* 将JSONObject转化导出到 Excel
* 这里遵循递归导出,当遇到数组时会调用 createSheet创建新的页面。
* @param jsonObject JSON对象
* @param savePath Excel保存路径
* @param excelName Excel名称
*/
public static void toExcelByJSONObject(JSONObject jsonObject, String savePath, String excelName){
try(XSSFWorkbook workbook = new XSSFWorkbook()){
// 获取当前的Sheet
XSSFSheet sheet = workbook.createSheet("sheet");
// 获取第一行
XSSFRow firstRow = sheet.createRow(0);
// 记录Key所在的列
Map<String, Integer> map = new HashMap<>();
// 记录列数
int cellCount = 0;
// 遍历 JSON的key
XSSFRow currentRow = sheet.createRow(1);
for (String key : jsonObject.keySet()) {
// 先处理列
if(!map.containsKey(key)){ // 当列不存在则添加
map.put(key, cellCount);
XSSFCell cell = firstRow.createCell(cellCount++);
cell.setCellValue(key);
}
XSSFCell currentCell = currentRow.createCell(map.get(key));
String jsonStr = jsonObject.getString(key);
// 如果 Value为数组 则创建新的 Sheet
if(JSON.isValidArray(jsonStr)){
String subSheetName = "Sheet-" + key;
createSubSheet(1, workbook, subSheetName, jsonObject.getJSONArray(key));
currentCell.setCellValue(subSheetName);
} else if(JSON.isValidObject(jsonStr)){ // 如果当前 value 仍然是一个JSON对象
String subSheetName = "Sheet-" + key;
createSubSheet(1, workbook, subSheetName, jsonObject.getJSONObject(key));
currentCell.setCellValue(subSheetName);
}
else {
// 特殊处理空值
if(StringUtils.isEmpty(jsonStr))
currentCell.setCellValue("null");
else
currentCell.setCellValue(jsonStr);
}
}
save(workbook, savePath, excelName);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/**
* 将 Excel对象保存到本地
* @param workbook Excel对象
* @param path Excel文件路径
* @param excelName excel名称
*/
public static void save(Workbook workbook, String path, String excelName){
try {
FileOutputStream fileOutputStream = new FileOutputStream(path +"/" + excelName +".xlsx");
workbook.write(fileOutputStream);
fileOutputStream.close();
System.out.println("保存完毕. 保存位置为[ " + path + "/" + excelName + " ]");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
String jsonPath = "C:\\Users\\unirithe\\IdeaProjects\\poi_demo\\src\\main\\resources\\data.json";
String savePath = "C:\\Users\\unirithe\\Desktop";
String excelName = "demo";
// 测试1
toExcelByLocalJSONFile(jsonPath, savePath, excelName + "1");
String jsonStr = readJSONFile(jsonPath);
JSONObject jsonObject = JSONObject.parseObject(jsonStr);
Object object = JSON.parse(jsonStr);
// 测试2
toExcelByString(jsonStr, savePath, excelName + "2");
// 测试3
toExcelByObject(object, savePath, excelName + "3");
// 测试4
toExcelByJSONObject(jsonObject, savePath, excelName + "4");
}
}
测试的JSON数据:
{
"班级A":[
{
"学号":"A01",
"语文":[
{
"文章":"课文1",
"作者":"李白"
},
{
"文章":"课文2",
"作者":"小李"
},
{
"文章":"课文2",
"作者": "小明"
}
],
"数学":"130"
},
{
"学号":"A02",
"语文":"130",
"数学":"135"
}
],
"班级B":[
{
"学号":"B01",
"语文":"128",
"数学":"135"
},
{
"学号":"B02",
"语文":"133",
"数学":"140"
}
]
}
测试结果如下,这里保存的demo1、demo2、demo3和demo4结果是一致的,主要是为了测试不同方法的正确性。