简介:这是一份面向C#开发者与数据处理初学者的Excel转JSON工具源码包,针对需要将结构化表格数据导入Web应用或NoSQL数据库的场景,提供可直接运行的转换方案。压缩包共36个文件,约193KB,包含6个cs源码文件、1个sln解决方案与csproj工程文件、3个exe可执行程序及配套dll、pdb调试文件,另有config配置、resx资源与json示例等,工程结构完整,便于直接编译调试。源码演示了通过OleDbConnection读取Excel、用OleDbDataAdapter填充DataTable,再借助JavaScriptSerializer序列化为JSON的完整链路,并提示了.xls与.xlsx连接字符串差异、异常处理、大数据分批读取及日期格式清洗等要点。目前已有936人学习下载,适合想快速掌握C#文件I/O与数据序列化、并需要可复用转换工具的开发者参考借鉴。
1. 从一份 Excel 配置表说起:这个 C# 小工具到底解决什么问题
策划丢过来一个 200 行的 Excel,里面有技能 ID、名称、描述、冷却时间、伤害系数,让你转成 JSON 塞进 Unity 或后端服务里。手动复制粘贴?改一版就要重来一次,字段一多眼睛都看花。这个用 C# 写的 Excel 转 JSON 工具,就是冲着这个场景来的:读一张.xlsx,按表头映射字段,输出一份干净的.json,支持批量转换和类型推断。它适合谁?做游戏配置表、做后台字典数据、做前端 mock 数据的开发者,尤其是那些不想为一次转换去装一堆 Python 环境或在线工具的人。工具本身是 WinForm 或控制台形态,源码完整,拿到就能编译,改字段映射规则也不用从零啃。
2. 技术选型与项目结构:为什么用 C# 而不是脚本
2.1 选型逻辑:C# 做配置转换的天然优势
很多人第一反应是 Python 的 pandas,读 Excel 确实方便,但问题在于分发。你给策划一个.exe,双击就能跑;给一个 Python 脚本,对方要先装解释器、再装 openpyxl,中间任何一步报错都来找你。C# 编译成单文件后,依赖可以内嵌,Windows 上零配置运行,这对内部工具来说是刚需。
这个项目读取 Excel 用的是EPPlus或NPOI,两者都是 .NET 生态里成熟的库。EPPlus 对.xlsx支持好、API 简洁,NPOI 则兼容老的.xls格式。常见做法是优先用 EPPlus,因为现在策划基本都用新版 Excel。JSON 序列化用Newtonsoft.Json,虽然 .NET 自带的System.Text.Json也能用,但 Newtonsoft 对动态类型和自定义格式化的支持更顺手,尤其是处理“空单元格要不要输出 null”这种细节时。
项目结构一般是这样组织的:
ExcelToJson/ ├── Program.cs // 入口,解析命令行参数或启动窗体 ├── Core/ │ ├── ExcelReader.cs // 读取 Excel,返回行数据 │ ├── JsonWriter.cs // 把行数据写成 JSON │ └── TypeInferrer.cs // 推断单元格类型(int/double/string/bool) ├── Models/ │ └── SheetConfig.cs // 配置类:表头行号、起始列、忽略列 ├── Config/ │ └── mapping.json // 字段映射规则(可选) └── ExcelToJson.csproj这个分层的好处是:读 Excel 的逻辑和写 JSON 的逻辑解耦,换一个 Excel 库只需要改ExcelReader,换 JSON 库只改JsonWriter。TypeInferrer单独抽出来是因为类型推断是最容易出玄学问题的地方——Excel 里一个单元格看着是数字,读出来可能是double,也可能是string,还可能是DateTime。
2.2 核心读取逻辑:从 Sheet 到内存对象
先看ExcelReader的关键代码。这里假设用 EPPlus,读取第一个 Sheet,跳过表头,把每一行转成Dictionary<string, object>:
using OfficeOpenXml; public class ExcelReader { // 读取指定 Sheet,返回行数据列表 public List<Dictionary<string, object>> Read(string filePath, int headerRow = 1) { var result = new List<Dictionary<string, object>>(); using var package = new ExcelPackage(new FileInfo(filePath)); var sheet = package.Workbook.Worksheets[0]; // 默认取第一个 Sheet int rowCount = sheet.Dimension.Rows; int colCount = sheet.Dimension.Columns; // 先读表头,建立列索引到字段名的映射 var headers = new string[colCount + 1]; for (int col = 1; col <= colCount; col++) { headers[col] = sheet.Cells[headerRow, col].Text?.Trim() ?? $"col{col}"; } // 从表头下一行开始读数据 for (int row = headerRow + 1; row <= rowCount; row++) { var dict = new Dictionary<string, object>(); for (int col = 1; col <= colCount; col++) { var cell = sheet.Cells[row, col]; // 跳过空行:整行都没有有效值 if (string.IsNullOrWhiteSpace(cell.Text)) continue; dict[headers[col]] = cell.Value; // 保留原始类型 } if (dict.Count > 0) result.Add(dict); } return result; } }这段代码有几个关键点。sheet.Dimension拿到的是有效数据区域,避免遍历整个 104 万行。cell.Value返回的是object,可能是double、string、bool、DateTime,保留原始类型是为了后面类型推断用。cell.Text是显示文本,用来判断空单元格更可靠,因为有些单元格值是null但格式还在。
参数说明:headerRow默认 1,如果你的表前几行是说明文字,改成 2 或 3 即可。Worksheets[0]是取第一个 Sheet,如果配置表在第二个 Sheet,改成Worksheets["SheetName"]或索引 1。
2.3 类型推断与 JSON 输出:把 object 变成正确的 JSON 类型
读出来的object直接序列化会出问题:Excel 里所有数字都是double,1会变成1.0,ID 字段就毁了。所以需要TypeInferrer做一次转换:
public static class TypeInferrer { // 根据单元格原始值和列名推断目标类型 public static object Infer(object rawValue, string columnName) { if (rawValue == null) return null; // 如果列名以 Id 结尾,强制转成整数 if (columnName.EndsWith("Id", StringComparison.OrdinalIgnoreCase)) { if (double.TryParse(rawValue.ToString(), out var idVal)) return (int)idVal; } // double 且是整数值,转 int if (rawValue is double d) { if (d == Math.Floor(d) && !double.IsInfinity(d)) return (int)d; return d; } // bool 直接返回 if (rawValue is bool b) return b; // DateTime 转成 ISO 字符串,避免时区问题 if (rawValue is DateTime dt) return dt.ToString("yyyy-MM-dd HH:mm:ss"); return rawValue.ToString(); } }逻辑说明:优先看列名,Id结尾的强制整数,这是业务约定。然后看值本身,double如果是整数值就转int,否则保留double。DateTime转字符串是为了 JSON 里可读,也避免不同平台解析差异。最后兜底转字符串。
JsonWriter就简单了,用 Newtonsoft 序列化:
using Newtonsoft.Json; public class JsonWriter { public void Write(List<Dictionary<string, object>> data, string outputPath) { // 先做类型推断 var converted = data.Select(row => row.ToDictionary( kv => kv.Key, kv => TypeInferrer.Infer(kv.Value, kv.Key) ) ).ToList(); // 格式化输出,方便人工检查 string json = JsonConvert.SerializeObject(converted, Formatting.Indented); File.WriteAllText(outputPath, json, System.Text.Encoding.UTF8); } }Formatting.Indented让 JSON 带缩进,方便策划直接看。Encoding.UTF8不加的话中文会乱码,这是血泪经验。如果数据量大、不需要人工看,改成Formatting.None能减小文件体积。
3. 跑起来:从编译到批量转换的完整操作
3.1 环境准备与编译
需要 .NET SDK,版本建议 6.0 或以上。如果你用 Visual Studio,直接打开.csproj还原 NuGet 包即可。命令行方式:
# 进入项目目录 cd ExcelToJson # 还原依赖 dotnet restore # 编译 Release 版本 dotnet build -c Release # 运行(假设是控制台程序,接收输入文件路径) dotnet run --project ExcelToJson.csproj -- "D:\config\skill.xlsx"编译产物在bin/Release/net6.0/下,ExcelToJson.exe可以直接拷给策划用。如果想让对方不装 .NET 运行时,发布时加--self-contained:
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true这样生成一个独立的 exe,体积大概 60-70MB,但省去了环境配置的沟通成本。
3.2 批量转换与命令行参数
单文件转换只是基础,实际项目里往往一个目录下几十张表。常见做法是在Program.cs里支持目录扫描:
static void Main(string[] args) { // 参数:输入路径(文件或目录),输出目录 string input = args.Length > 0 ? args[0] : "."; string outputDir = args.Length > 1 ? args[1] : "./output"; Directory.CreateDirectory(outputDir); var files = new List<string>(); if (Directory.Exists(input)) files.AddRange(Directory.GetFiles(input, "*.xlsx")); else if (File.Exists(input)) files.Add(input); var reader = new ExcelReader(); var writer = new JsonWriter(); foreach (var file in files) { var data = reader.Read(file); string outName = Path.GetFileNameWithoutExtension(file) + ".json"; string outPath = Path.Combine(outputDir, outName); writer.Write(data, outPath); Console.WriteLine($"转换完成: {file} -> {outPath}"); } }参数说明:第一个参数是输入,可以是单个.xlsx文件,也可以是一个目录。第二个参数是输出目录,默认./output。Directory.GetFiles只扫当前层,不递归子目录,如果需要递归改成SearchOption.AllDirectories。
3.3 输出结果验证
转换完成后,打开 JSON 检查三件事:字段名是否和表头一致、数字类型是否正确(ID 没有小数点)、中文是否乱码。如果 ID 出现1.0,说明TypeInferrer没生效,检查列名是否以Id结尾,或者手动在映射配置里指定类型。
提示:Excel 里如果单元格格式是“文本”,读出来的值可能是字符串
"123",TypeInferrer里double.TryParse能处理,但如果是"123abc"就会失败,返回原字符串。这种情况需要在 Excel 里先清理数据。
4. 避坑与常见问题:那些让我加班到凌晨的细节
4.1 空单元格与合并单元格的坑
现象:转换出来的 JSON 里某些字段缺失,或者值跑到了错误的字段上。原因通常是合并单元格。EPPlus 读取合并区域时,只有左上角单元格有值,其他位置是null。如果表头有合并,headers[col]会变成空字符串,导致字段名丢失。解决办法是在读表头时,遇到空字符串就往前找最近的非空表头:
for (int col = 1; col <= colCount; col++) { string header = sheet.Cells[headerRow, col].Text?.Trim(); if (string.IsNullOrEmpty(header)) header = headers[col - 1]; // 继承前一列的表头 headers[col] = header; }4.2 数字精度丢失与科学计数法
现象:Excel 里填的1234567890123转出来变成1.234567890123E+12或者末尾几位变 0。原因是 Excel 内部用double存储数字,超过 15 位有效数字就会丢精度。解决方式:ID 类字段在 Excel 里设置成文本格式,读取时cell.Text拿字符串,不要用cell.Value。或者在TypeInferrer里对Id字段直接用long.TryParse处理字符串。
4.3 中文乱码与 BOM 头
现象:JSON 文件用记事本打开正常,用某些编辑器打开中文变问号。原因是File.WriteAllText默认不带 BOM,部分老编辑器识别不了 UTF-8。解决:显式指定new UTF8Encoding(true)带 BOM,或者统一要求用 VS Code 打开。我一般会在输出时加 BOM,省得策划来找。
4.4 大文件内存溢出
现象:一张几万行的 Excel 转换时程序卡死或抛OutOfMemoryException。原因是ExcelPackage把整个文件加载到内存,加上List<Dictionary>又复制一份。解决:改用流式读取,或者分批处理。EPPlus 支持ExcelPackage.LicenseContext设置后按行读取,但更简单的做法是限制单次转换的行数,或者换用 NPOI 的SXSSF模式(仅写)。读方面,几万行一般还能扛住,几十万行就要考虑分 Sheet 了。
4.5 字段名大小写与特殊字符
现象:JSON 里字段名是SkillID,但代码里按skillId取,取不到。原因是 Excel 表头大小写和代码约定不一致。解决:在ExcelReader里统一转成 camelCase,或者加一层映射配置。特殊字符如空格、括号,建议在表头就规范掉,不要留到代码里做替换。
5. 二次开发:把工具改成你项目里的样子
5.1 自定义字段映射与类型覆盖
默认的类型推断是通用规则,但每个项目都有自己的约定。比如有的项目要求所有float字段保留两位小数,有的要求枚举字段输出字符串而不是数字。改造思路是在Config/mapping.json里加一层覆盖规则:
{ "skill.xlsx": { "Id": "int", "Name": "string", "Cooldown": "float2", "Type": "enum" } }然后在TypeInferrer里读取这个配置,优先按配置转换。float2表示保留两位小数的浮点数,enum表示输出枚举名字符串。这样策划改表不用改代码,你也不用每次手动调。
5.2 接入 Unity 或后端服务的自动化流程
工具跑通后,下一步是把它塞进构建流程。Unity 项目里可以写一个Editor脚本,在AssetPostprocessor里监听 Excel 文件变化,自动调用这个转换工具,把 JSON 输出到Resources或StreamingAssets。后端服务可以在 CI 里加一步dotnet run,把配置表转成 JSON 后打包进 Docker 镜像。关键是把输入输出路径参数化,不要硬编码。
5.3 验证转换结果的一个小技巧
改完代码后怎么确认没改坏?我一般会准备一张“黄金测试表”,里面覆盖各种边界:空单元格、合并单元格、超长数字、中文、特殊字符、布尔值。每次改完跑一遍,用diff对比输出 JSON 和预期结果。如果差异只在预期内,就放心提交。这个习惯帮我省了很多次回滚。
注意:
diff对比时记得忽略行尾空格和 BOM 差异,否则会误报。
5.4 从工具到库的演进
如果团队里多个项目都要用,可以把核心逻辑抽成 NuGet 包,只暴露ExcelToJsonConverter.Convert(input, output, config)一个入口。这样其他项目引用包就行,不用拷贝源码。抽包时注意把 EPPlus 和 Newtonsoft 的依赖标记为PrivateAssets,避免版本冲突。
从那以后我每次改转换规则,都强制先跑一遍黄金测试表,再对比 diff,确认无误才提交。希望这个工具和这些踩坑记录能帮到你,少加几次班。
本文还有配套的精品资源,点击获取