• Java实现将JSON文件导出到Excel


    一、运行环境


    • windows10
    • IDEA 2022
    • JDK 8
    • Maven 3.8.6
    • Apache POI 5
    • fastjson2

    二、需求描述


    写一个功能,任意json生成excel,每个数组都单独生成一个sheet。

    三、实现思路


    参考资料:Apache POI 使用教程

    主要实现思路: 使用支持Java对象与JSON对象、字符串互相转换的fastjson,以及支持Java将JSON转化Excel的库 apache-poi

    Excel表格关键结构:

    • Workbook 工作台,相当于一个 excel文件
    • sheet,一个excel文件中的表格页面,可能有多个
      • row,所在sheet中的行
        • cel,所在sheet中所在行的列
          • value,所在单元格的值

    在这里插入图片描述

    JSON转换的几种情形与实现思路:

    情形一:普通的单层结构,多个JSON对象

    {
        "班级A" : [{
          "文章":"课文1",
          "作者":"李白"
        },
        {
          "文章":"课文2",
          "作者":"小李"
        },
        {
          "文章":"课文2",
          "作者": "小明"
        }]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    导出结果:

    在这里插入图片描述
    当我们使用fastjson遍历JSONObject时,每次读取到的都是单个{ } 所包含的对象,比如:

    {
      "文章":"课文1",
      "作者":"李白"
    }
    
    • 1
    • 2
    • 3
    • 4

    这种情况下,我们在Excel的Sheet中的行是确定的,比如这里就是第二行(第一行是列名),行根据遍历的顺序确定,而列则是不确定的,在这里有 “文章”,“作者” 这两个列,但是一开始这两个列是不存在的。这里则确定文章在第一列,作者按第二列(默认升序排序)。

    当遍历下一个对象时,我们可能遇到旧的列,也可能遇到新的列,比如:

    {
      "文章":"课文2",
      "作者":"李白",
      "出版日期": "2022年7月6日"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这时,我们需要知道"文章" 和 “作者” 在第几列,同时也要知道 “出版日期” 应该在第几列,否则就不能确定唯一的单元格,将 JSON的value存储进去。

    这里可以使用 Map<String, Integer> map 来记录列名以及下标。

    在遍历对象时,key是列名,value则是单元格该填的值,如果 map.get(key) 的结果是空的,说明该列不存在,则需要创建,如果存在,那么可以创建单元格的对象,将值填入即可。

    情形二:嵌套结构,JSON数组的嵌套

    {
      "班级A":[
        {
          "学号":"A01",
          "语文":[
            {
              "文章":"课文1",
              "作者":"李白"
            },
            {
              "文章":"课文2",
              "作者":"小李"
            },
            {
              "文章":"课文2",
              "作者": "小明"
            }
          ],
          "数学":"130"
        },
        {
          "学号":"A02",
          "语文":"130",
          "数学":"135"
        }
      ],
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    实现效果:

    在这里插入图片描述

    在这里插入图片描述

    这里相比之前的情况复杂了一些,主要的就是需要再次创建一个新的 sheet,这意味着需要使用递归完成创建,于是我们可以将之前那种情形的代码实现封装成一个方法,比如createSubSheet(),在遍历JSON对象时,如果value值是一个JSONAarray,那么就再次调用createSubSheet()这个方法,只要使用同一个Workbook对象,表示同一个excel文件,就能满足这个需求了。

    四、实现代码


    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <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.0</modelVersion>
    
        <groupId>groupId</groupId>
        <artifactId>poi_demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>5.2.2</version>
            </dependency>
    
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.9</version>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>2.0.7</version>
            </dependency>
    
        </dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    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 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 fistRow = sheet.createRow(0);
            // 获取每一项
            for (int row = 1; row <= jsonArray.size(); row++) {
                // 转为 数组
                JSONArray array = jsonArray.getJSONArray(row - 1);
                // 遍历每个JSON对象的所有KV
                for (int i1 = 0; i1 < array.size(); i1++) {
                    JSONObject obj = array.getJSONObject(i1);
                    // 创建行
                    XSSFRow currentRow = sheet.createRow(i1 + 1);
                    // 遍历每个KV
                    for (String cellName : obj.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(obj.get(cellName));
                        // 如果V为数组则递归创建sheet
                        if(JSON.isValidArray(cellValue)){
                            String subCellName = "Sheet" + layer + "-" + sheetName + "-" + cellName;
                            cell.setCellValue(subCellName);
                            createSubSheet(layer + 1, workbook,subCellName, JSONArray.of(obj.get(cellName)));
                        }
                        else{
                            cell.setCellValue(obj.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 value = jsonObject.getString(key);
                    // 如果 Value为数组 则创建新的 Sheet
                    if(JSON.isValidArray(value)){
                        createSubSheet(1, workbook, key, JSONArray.of(jsonObject.get(key)));
                        currentCell.setCellValue("Sheet-" + key);
                    } else{
                        // 特殊处理空值
                        if(StringUtils.isEmpty(value))
                            currentCell.setCellValue("null");
                        else
                            currentCell.setCellValue(value);
                    }
                }
                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\\test.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");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212

    测试的JSON数据:

    {
      "班级A":[
        {
          "学号":"A01",
          "语文":[
            {
              "文章":"课文1",
              "作者":"李白"
            },
            {
              "文章":"课文2",
              "作者":"小李"
            },
            {
              "文章":"课文2",
              "作者": "小明"
            }
          ],
          "数学":"130"
        },
        {
          "学号":"A02",
          "语文":"130",
          "数学":"135"
        }
      ],
      "班级B":[
        {
          "学号":"B01",
          "语文":"128",
          "数学":"135"
        },
        {
          "学号":"B02",
          "语文":"133",
          "数学":"140"
        }
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    测试结果如下,这里保存的demo1、demo2、demo3和demo4结果是一致的,主要是为了测试不同方法的正确性。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    教你使用CANN将照片一键转换成卡通风格
    《优化接口设计的思路》系列:第二篇—接口用户上下文的设计与实现
    当后端给我返回了302状态码
    Monitoring Database Activity监控数据库活动
    涿州8500亩 国稻种芯·中国水稻节:河北保定百尺竿镇名片
    leetcode621. 任务调度器
    第11章 Java集合(二)
    pdffactory pro 8中文破解版
    基于单片机的空气质量检测系统
    去中心化数字身份为什么在元宇宙中这么重要
  • 原文地址:https://blog.csdn.net/Unirithe/article/details/125635059