资讯详情

C# WinForm 实战:JSON 数据转 Excel 一键导出与嵌套处理

📅 2026/10/13 21:11:44 | 华诺云谱 👁 阅读
C# WinForm 实战:JSON 数据转 Excel 一键导出与嵌套处理
简介这是一份面向 WinForm 开发初学者与需要处理数据格式转换的 .NET 程序员的实用工具源码聚焦于将 JSON 数据解析并导出为 Excel 表格这一常见需求。资源以 C# 项目形式组织包含完整的工程文件与可执行程序方便直接运行验证或二次开发。压缩包共 61 个文件约 192KB其中 14 个 .cs 源码文件承载核心逻辑另有 6 个 exe 可执行文件、6 个 application 与 manifest 清单、5 个 config 配置、4 个 pdb 调试符号及 resx、ico、csproj 等工程资源结构完整覆盖从界面设计到数据转换的各个环节。项目内 JsonCls.cs 与 ChangeToExcel.cs 分别承担 JSON 解析与 Excel 写入职责Form1 相关文件负责界面交互便于读者理解 WinForm 与文件处理库的配合方式。目前已有 825 人学习下载适合作为课程设计、小型工具开发或格式转换功能的参考实现帮助快速掌握 JSON 到 Excel 的转换思路与工程组织方式。1. JSON 数据转 Excel 的 WinForm 落地从手工复制到一键导出后台接口返回一坨 JSON运营同事要 Excel日志系统吐出嵌套 JSON财务要按字段对账爬虫抓下来的半结构化数据老板要一张能筛选的报表。手工复制粘贴不仅慢遇到几百条嵌套数组直接翻车。JSON 数据转 Excel 的 WinForm 方案本质是把「解析 JSON → 拍平成二维表 → 写入 xlsx」这条链路封装进一个桌面程序让非技术同事双击就能用。它适合两类人一类是经常被临时导出需求打断的 .NET 开发者另一类是想给自己工具加个导出按钮的桌面端维护者。下面按「先跑通最小闭环再处理嵌套和类型最后讲避坑」的顺序拆开讲源码结构也会一并说明。2. 最小可运行闭环WinForm 里把 JSON 数组写进 xlsx这一章先把链路跑通不追求功能完整。核心是三件事选解析库、选写表库、把两者接起来。很多新手卡在第一步就选错库后面嵌套处理会非常痛苦。2.1 解析与写表选型为什么是 System.Text.Json 加 ClosedXML.NET 生态里 JSON 解析常见三选一Newtonsoft.Json、System.Text.Json、LitJson。写 Excel 常见三选一NPOI、EPPlus、ClosedXML。我的组合是 System.Text.Json ClosedXML理由如下。System.Text.Json 是 .NET 内置不需要额外引 NuGet 包就能用JsonDocument做只读遍历性能比 Newtonsoft 好一截尤其在大数组场景。它的JsonElement支持EnumerateArray和EnumerateObject处理不定结构很顺手。缺点是序列化到自定义类型时对大小写敏感需要配PropertyNameCaseInsensitive这个后面避坑章会讲。写 Excel 这边NPOI 功能最全但 API 偏底层写一个单元格要CreateRow、CreateCell、SetCellValue三连EPPlus 从 5.0 起商业授权收紧公司项目用要留意ClosedXML 基于 OpenXML 封装API 接近「所见即所得」cell.Value xxx直接赋值插入表头、自动列宽、冻结首行都是一行代码。对于「JSON 转 Excel」这种以导出为主的场景ClosedXML 的开发效率明显更高。提示如果项目已经深度依赖 NPOI不必为了这个功能换库NPOI 同样能完成只是代码量多三成。2.2 建工程与装包三条命令搞定依赖新建一个 Windows 窗体应用.NET 6 或 .NET Framework 4.7.2 以上都行然后装两个包。在「程序包管理器控制台」里执行# 安装 ClosedXML用于写 xlsx Install-Package ClosedXML # System.Text.Json 在 .NET 6 已内置.NET Framework 项目需手动装 Install-Package System.Text.Json如果用 .NET CLI等价命令是dotnet add package ClosedXML dotnet add package System.Text.Json装完检查 csproj确认TargetFramework是net6.0-windows或更高。ClosedXML 依赖 DocumentFormat.OpenXml装包时会自动带上不要手动再装一遍否则容易出现版本冲突导致运行时抛FileLoadException。2.3 核心转换代码从 JsonElement 到 DataTable 再到工作表界面放一个按钮btnConvert、一个文本框txtJson、一个按钮btnExport。核心逻辑分两步先把 JSON 转成DataTable再把DataTable写进 xlsx。为什么中间要过一层 DataTable因为它天然是二维结构列名即表头行即数据还能直接绑到 DataGridView 预览调试时肉眼可见。using System.Data; using System.Text.Json; using ClosedXML.Excel; public static class JsonToExcel { // 把 JSON 数组文本转成 DataTable public static DataTable ParseToTable(string json) { var table new DataTable(); using var doc JsonDocument.Parse(json); var root doc.RootElement; // 只处理顶层数组对象数组是导出场景最常见形态 if (root.ValueKind ! JsonValueKind.Array) throw new ArgumentException(根节点必须是数组); bool headerBuilt false; foreach (var item in root.EnumerateArray()) { if (item.ValueKind ! JsonValueKind.Object) continue; // 第一遍用第一条对象的键建列 if (!headerBuilt) { foreach (var prop in item.EnumerateObject()) { // 列名重复时加后缀避免 DataTable 抛异常 string colName prop.Name; int suffix 1; while (table.Columns.Contains(colName)) colName ${prop.Name}_{suffix}; table.Columns.Add(colName, typeof(string)); } headerBuilt true; } // 第二遍按列名取值缺失字段填空 var row table.NewRow(); foreach (DataColumn col in table.Columns) { if (item.TryGetProperty(col.ColumnName, out var val)) row[col] ElementToString(val); else row[col] string.Empty; } table.Rows.Add(row); } return table; } // 把任意 JsonElement 转成可读字符串嵌套结构序列化回 JSON 文本 private static string ElementToString(JsonElement el) { return el.ValueKind switch { JsonValueKind.String el.GetString() ?? , JsonValueKind.Number el.GetRawText(), JsonValueKind.True true, JsonValueKind.False false, JsonValueKind.Null , _ el.GetRawText() // 对象或数组保留原始 JSON }; } // 把 DataTable 写成 xlsx public static void WriteExcel(DataTable table, string path) { using var wb new XLWorkbook(); var ws wb.Worksheets.Add(Sheet1); // 写表头 for (int c 0; c table.Columns.Count; c) ws.Cell(1, c 1).Value table.Columns[c].ColumnName; // 写数据 for (int r 0; r table.Rows.Count; r) for (int c 0; c table.Columns.Count; c) ws.Cell(r 2, c 1).Value table.Rows[r][c]?.ToString() ?? ; ws.Row(1).Style.Font.Bold true; ws.SheetView.FreezeRows(1); ws.Columns().AdjustToContents(); wb.SaveAs(path); } }按钮事件里串起来private void btnExport_Click(object sender, EventArgs e) { try { var table JsonToExcel.ParseToTable(txtJson.Text); using var sfd new SaveFileDialog { Filter Excel|*.xlsx }; if (sfd.ShowDialog() DialogResult.OK) { JsonToExcel.WriteExcel(table, sfd.FileName); MessageBox.Show($导出 {table.Rows.Count} 行); } } catch (Exception ex) { MessageBox.Show(导出失败 ex.Message); } }逻辑说明ParseToTable用第一条对象建列后续对象按列名取值缺字段填空字符串这样能容忍字段不一致的脏数据。ElementToString对嵌套对象和数组直接GetRawText()保留原始 JSON 文本避免信息丢失。WriteExcel里FreezeRows(1)冻结表头AdjustToContents自动列宽这两个是导出体验的关键少了会被用户吐槽。参数说明JsonDocument.Parse默认对深度有限制64 层超深嵌套会抛异常需要传JsonDocumentOptions { MaxDepth 128 }。AdjustToContents在列数很多时性能下降明显超过 50 列建议改成固定宽度。3. 嵌套结构与类型处理让导出结果能直接对账最小闭环跑通后真实数据会立刻给你上强度字段里套对象、数组里套数组、数字被当字符串、日期是时间戳。这一章解决这些。3.1 嵌套对象拍平点号拼接列名接口返回{user:{name:A,age:18}}直接导出会得到一列user内容是{name:A,age:18}财务没法用。常见做法是递归拍平用点号拼列名变成user.name和user.age两列。// 递归拍平嵌套对象前缀用点号连接 private static void Flatten(JsonElement obj, string prefix, DataRow row, DataTable table) { foreach (var prop in obj.EnumerateObject()) { string colName string.IsNullOrEmpty(prefix) ? prop.Name : ${prefix}.{prop.Name}; if (prop.Value.ValueKind JsonValueKind.Object) { Flatten(prop.Value, colName, row, table); // 递归下钻 } else { if (!table.Columns.Contains(colName)) table.Columns.Add(colName, typeof(string)); row[colName] ElementToString(prop.Value); } } }调用时先建行再递归注意列是动态增加的所以要在写行之前把所有对象的列都扫一遍否则会出现「后面的对象有列但前面行没值」的错位。稳妥做法是两遍扫描第一遍只建列第二遍填值。3.2 数组字段的三种处理策略数组字段没有万能解取决于业务。我一般给三种策略让用户在下拉框里选策略适用场景输出形态序列化保留数组内容仅作参考单元格内放 JSON 文本展开成多行数组是明细行主表字段重复数组每项一行拼接成字符串数组是标签类用逗号连接成a,b,c展开成多行是最容易出 bug 的因为主表字段要复制。实现时对每个数组项克隆一份主行数据再追加数组项字段。注意数组项本身可能又是对象需要再走一次拍平。3.3 类型与格式数字、日期、布尔别全塞成文本全用typeof(string)最省事但导出后 Excel 里数字是文本不能求和日期是字符串不能排序。改进做法是建列时推断类型整数用typeof(long)小数用typeof(double)ISO 日期用typeof(DateTime)。推断失败就退回字符串。// 根据 JsonValueKind 推断列类型 private static Type InferType(JsonElement val) { return val.ValueKind switch { JsonValueKind.Number val.TryGetInt64(out _) ? typeof(long) : typeof(double), JsonValueKind.True or JsonValueKind.False typeof(bool), JsonValueKind.String DateTime.TryParse(val.GetString(), out _) ? typeof(DateTime) : typeof(string), _ typeof(string) }; }写 Excel 时对 DateTime 列设NumberFormat.Format yyyy-mm-dd hh:mm:ss对数字列设#,##0.00。这样导出结果能直接对账不用再手工转格式。4. 避坑与排查导出翻车的五个高频现场这一章全是血泪经验每条按「现象 → 原因 → 解决」写遇到对应症状直接对号入座。4.1 中文乱码现象是表头变问号现象导出的 xlsx 打开后中文列名显示为???或方块。原因JSON 文本来源的编码不是 UTF-8或者读取文件时用了Encoding.Default。解决读文件统一用File.ReadAllText(path, Encoding.UTF8)如果 JSON 来自 HTTP确认响应头Content-Type带charsetutf-8。ClosedXML 写 xlsx 本身是 UTF-8问题几乎都出在读取环节。4.2 大文件内存爆掉现象是导出 10 万行时程序卡死现象数据量上万后进度条不动内存飙到几个 G 然后 OOM。原因JsonDocument会把整个 JSON 加载进内存DataTable再复制一份ClosedXML 写的时候又建一份 DOM三份叠加。解决改用JsonSerializer.DeserializeAsyncEnumerable流式读取边读边写或者分批写入每 5000 行SaveAs一次再追加。ClosedXML 不支持追加大文件场景建议换DocumentFormat.OpenXml的SAX模式内存占用能降一个数量级。4.3 列顺序错乱现象是导出列顺序和 JSON 不一致现象明明 JSON 里name在前age在后导出后反了。原因DataTable列顺序按添加顺序如果用了Dictionary或HashSet去重顺序就丢了。解决建列时用Liststring记录首次出现顺序再按这个顺序table.Columns.Add。不要依赖DataTable.Columns的默认排序。4.4 日期变数字现象是导出后日期显示为 45000 这种整数现象JSON 里是2024-01-01导出后单元格显示45292。原因列类型推断成了DateTime但写入时传的是字符串ClosedXML 按 OADate 解析了。解决写入前显式DateTime.Parse或者干脆把日期列当字符串写再在 Excel 里设格式。我一般选后者省去时区转换的麻烦。4.5 特殊字符导致公式注入现象是单元格以 开头被 Excel 当公式执行现象某字段值是11导出后 Excel 直接算出 2甚至cmd|...这种会被当命令。原因Excel 把以、、-、开头的文本当公式。解决写入前对这类值加单引号前缀或者设单元格DataType XLDataType.Text。这是安全底线导出用户可控数据时必须处理。5. 进阶技巧让导出工具从能用变成好用到这一步功能已经完整但离「同事愿意天天用」还差几个细节。这一章讲我踩过坑之后固定下来的几个习惯。第一个是配置化列映射。硬编码列名在字段变动时就要改代码重新发版我一般把「JSON 路径 → 中文表头 → 格式」做成一个 JSON 配置文件程序启动时读进来。这样运营改表头不用找开发。配置长这样{ columns: [ { path: user.name, header: 用户名, format: text }, { path: amount, header: 金额, format: #,##0.00 }, { path: created_at, header: 创建时间, format: yyyy-mm-dd } ] }程序按path用点号逐层下钻取值取不到就填空。这样列顺序、表头、格式全在配置里代码只负责遍历。第二个是导出前预览。在窗体上加一个DataGridView绑定DataTable用户点「预览」先看前 100 行确认列没错再导出。这个改动很小但能挡掉八成「导错了」的返工。第三个是异常兜底。JsonDocument.Parse对格式错误的 JSON 会抛JsonException带行号和位置。我一般捕获后把ex.LineNumber和ex.BytePositionInLine显示出来用户自己就能定位是哪一行少了逗号。比一句「JSON 格式错误」有用得多。第四个是进度反馈。大文件导出时用BackgroundWorker或Task.Run把转换放后台主线程更新进度条避免界面假死。注意 ClosedXML 的XLWorkbook不是线程安全的所有写操作必须在同一个线程里完成不要边写边更新 UI 直接操作 workbook。验证方法很简单拿一份真实接口返回的 JSON故意删掉一个字段、加一个嵌套对象、塞一个11跑一遍看导出结果是否符合预期。能过这三关基本就能交付了。我自己最大的教训是早期图省事全用字符串列结果财务拿到表后手动转数字转了半天回头找我返工。从那以后我固定「能推断类型就推断推断不了才退字符串」并且导出后自己先打开看一眼再发出去。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑