资讯动态

C#实现Excel转JSON工具:配置表驱动开发的数据转换方案

发布时间:2026/10/20 12:43:54 来源:尧图企业网站定制
简介这是一份面向C#开发者与数据处理人员的Excel转JSON工具源码包适合需要在Web应用或NoSQL数据库导入结构化数据时快速完成格式转换的初中级程序员。压缩包共36个文件约193KB包含6个cs源码文件、1个csproj工程文件与1个sln解决方案另有exe、dll、pdb等编译产物及config、resx、json等配置资源构成一套可直接运行的完整Visual Studio项目。源码演示了通过OleDbConnection读取Excel、用OleDbDataAdapter填充DataTable再借助JavaScriptSerializer序列化为JSON并写入文件的完整链路同时提示了.xls与.xlsx连接字符串差异、异常处理、分批读取与数据清洗等实践要点。目前已有936人学习下载读者可据此掌握文件I/O与数据序列化思路并封装为可复用工具类或替换为EPPlus、NPOI等第三方库。1. 一个把 Excel 表格变成 JSON 的 C# 小工具为什么值得你拆一遍如果你做过配置表驱动的项目大概率经历过这种场面策划在 Excel 里改完数值程序这边要手动复制粘贴成 JSON改一次错一次最后谁也不知道线上跑的是哪版数据。这个用 C# 实现的 Excel 转 JSON 工具解决的就是这个链路问题——它把 .xlsx 文件按表头映射成结构化 JSON支持多 Sheet、类型推断和批量导出适合做游戏配置管线、后台数据初始化、接口 Mock 数据生成这类场景。技术栈是 .NET C#核心依赖 Excel 数据读取库和 JSON 序列化库整个项目结构不复杂但麻雀虽小配置表转换里那些坑一个不少。接下来我按「能干什么 → 怎么跑 → 怎么改」的顺序把这份源码拆开讲。2. 项目结构与技术选型从 Excel 到 JSON 的数据链路2.1 整体架构三层职责划分这个工具虽然体量不大但结构上做了清晰的职责分离我把它拆成三层来看读取层负责打开 Excel 文件、定位 Sheet、读取单元格数据。常见做法是用 NPOI 或 EPPlus 这类库前者对 .xls 和 .xlsx 都兼容后者 API 更现代但主要支持 .xlsx。源码里如果用的是 NPOI说明作者考虑到了老格式兼容如果用 EPPlus则更偏向新项目。转换层把二维表格数据映射成字典或对象。这里的关键是表头解析——第一行通常是字段名第二行可能是类型声明第三行开始才是数据。转换层要处理空单元格、合并单元格、类型转换字符串转数字/布尔这些脏活。输出层把转换后的对象序列化成 JSON 字符串写入文件或返回给调用方。序列化一般用 System.Text.Json 或 Newtonsoft.Json前者是 .NET 内置后者生态更成熟。三层之间通过接口或委托解耦读取层不关心输出格式转换层不关心数据来源。这种设计的好处是以后要加 CSV 导出或数据库导入只需要替换读取层或输出层转换逻辑不用动。2.2 目录结构文件都在干什么一个典型的项目目录大概长这样ExcelToJson/ ├── Program.cs # 入口解析命令行参数 ├── Services/ │ ├── ExcelReader.cs # Excel 读取封装 │ ├── JsonConverter.cs # 表格到 JSON 的转换逻辑 │ └── FileWriter.cs # 输出文件写入 ├── Models/ │ ├── SheetConfig.cs # Sheet 配置模型 │ └── CellData.cs # 单元格数据模型 ├── Utils/ │ └── TypeHelper.cs # 类型推断与转换工具 └── appsettings.json # 配置文件可选Program.cs是入口负责解析命令行参数比如输入文件路径、输出目录、是否合并 Sheet 等。Services目录下是核心业务逻辑Models放数据模型Utils放通用工具。如果你拿到源码发现目录名不一样不用纠结按这个职责去对应就行。2.3 技术选型为什么是 NPOI System.Text.JsonExcel 读取库的选择上NPOI 的优势是免费开源、支持 .xls 和 .xlsx、不依赖 Office 安装。EPPlus 从 5.0 开始商业授权变严如果是公司项目要注意合规。源码里如果用了 NPOI那基本可以确定作者考虑到了跨平台和授权问题。JSON 序列化方面System.Text.Json 是 .NET Core 之后的内置库性能好、内存占用低但默认不支持循环引用和某些复杂类型。Newtonsoft.Json 功能更全但多一个依赖。如果项目目标是 .NET 6用 System.Text.Json 更轻量如果要兼容老项目Newtonsoft.Json 更稳妥。提示如果你在 .NET Framework 4.x 下跑NPOI 和 Newtonsoft.Json 的组合兼容性最好如果是 .NET 6/8System.Text.Json 加 NPOI 是更现代的选择。3. 核心模块拆解读取、转换、序列化的完整链路3.1 Excel 读取打开文件与定位 Sheet读取层的第一步是打开工作簿。用 NPOI 的写法大概是这样using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; public IWorkbook OpenWorkbook(string filePath) { using var fs new FileStream(filePath, FileMode.Open, FileAccess.Read); // 根据扩展名判断用 XSSF 还是 HSSF if (filePath.EndsWith(.xlsx)) return new XSSFWorkbook(fs); else return new HSSFWorkbook(fs); }这段代码做了两件事用FileStream以只读方式打开文件然后根据扩展名选择XSSFWorkbook.xlsx或HSSFWorkbook.xls。注意using声明确保文件句柄用完释放否则在 Windows 上会锁文件后续想覆盖写入就会报错。定位 Sheet 的逻辑通常是按名称或索引public ISheet GetSheet(IWorkbook workbook, string sheetName) { // 优先按名称查找找不到再按索引 var sheet workbook.GetSheet(sheetName); if (sheet null) sheet workbook.GetSheetAt(0); // 默认第一个 return sheet; }参数说明sheetName是配置里指定的 Sheet 名如果为空则回退到第一个 Sheet。这个回退逻辑很实用因为很多配置表只有一个 Sheet不需要额外指定。3.2 表头解析与类型推断表头解析是转换层的核心。常见的表格结构是第一行字段名第二行类型string/int/bool第三行开始数据。解析逻辑如下public ListDictionarystring, object ParseSheet(ISheet sheet) { var result new ListDictionarystring, object(); var headerRow sheet.GetRow(0); var typeRow sheet.GetRow(1); // 类型行可能为空 int colCount headerRow.LastCellNum; // 收集字段名和类型 var fields new List(string Name, string Type)(); for (int i 0; i colCount; i) { var nameCell headerRow.GetCell(i); var typeCell typeRow?.GetCell(i); string name nameCell?.ToString() ?? $col_{i}; string type typeCell?.ToString() ?? string; fields.Add((name, type)); } // 从第三行开始读数据 for (int r 2; r sheet.LastRowNum; r) { var row sheet.GetRow(r); if (row null) continue; var dict new Dictionarystring, object(); for (int c 0; c fields.Count; c) { var cell row.GetCell(c); dict[fields[c].Name] ConvertCell(cell, fields[c].Type); } result.Add(dict); } return result; }这段代码的关键点headerRow.LastCellNum拿到列数typeRow可能为 null 所以用了空条件运算符。数据行从索引 2 开始第三行每行构造一个字典。ConvertCell负责把单元格值按类型转换后面单独讲。3.3 单元格类型转换空值、日期、布尔的处理ConvertCell是踩坑最多的地方因为 Excel 的单元格类型很丰富private object ConvertCell(ICell cell, string type) { if (cell null) return null; switch (cell.CellType) { case CellType.String: return cell.StringCellValue; case CellType.Numeric: if (DateUtil.IsCellDateFormatted(cell)) return cell.DateCellValue.ToString(yyyy-MM-dd HH:mm:ss); return cell.NumericCellValue; case CellType.Boolean: return cell.BooleanCellValue; case CellType.Formula: // 公式单元格按缓存值处理 return cell.CachedFormulaResultType switch { CellType.Numeric cell.NumericCellValue, CellType.String cell.StringCellValue, _ cell.ToString() }; default: return cell.ToString(); } }逻辑说明先判空再按CellType分支。数值类型要额外判断是不是日期格式因为 Excel 里日期本质是数字不判断的话会输出一串浮点数。公式单元格用CachedFormulaResultType拿缓存值避免重新计算公式。type参数在这里其实没直接用上因为 NPOI 已经能判断单元格类型但如果你的表格里类型行和实际值不一致可以在这里加一层强制转换。注意日期格式的判断依赖单元格的格式设置如果 Excel 里日期列被设成「常规」IsCellDateFormatted会返回 false输出就是数字。这种情况要么在 Excel 里改格式要么在代码里按字段名硬编码日期字段。3.4 JSON 序列化与输出转换完的ListDictionarystring, object直接序列化using System.Text.Json; public string ToJson(ListDictionarystring, object data) { var options new JsonSerializerOptions { WriteIndented true, // 格式化输出 Encoder JavaScriptEncoder.UnsafeRelaxedJsonEscaping // 中文不转义 }; return JsonSerializer.Serialize(data, options); }参数说明WriteIndented让 JSON 带缩进方便人看UnsafeRelaxedJsonEscaping让中文直接输出而不是转成\uXXXX。如果你用 Newtonsoft.Json对应的是Formatting.Indented和StringEscapeHandling.EscapeNonAscii的反向设置。输出到文件public void WriteToFile(string json, string outputPath) { var dir Path.GetDirectoryName(outputPath); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); File.WriteAllText(outputPath, json, Encoding.UTF8); }先确保目录存在再写文件。用 UTF-8 编码避免中文乱码。4. 跑起来环境准备、命令行参数与批量导出4.1 环境准备与依赖安装跑这个项目需要 .NET SDK版本看源码的.csproj文件。如果是net6.0或net8.0去装对应 SDK如果是net472需要 Visual Studio 或 MSBuild。依赖库通过 NuGet 安装dotnet add package NPOI dotnet add package System.Text.Json如果源码里用的是 Newtonsoft.Json把第二行换成dotnet add package Newtonsoft.Json。装完dotnet restore拉齐依赖然后dotnet build编译。4.2 命令行参数与单文件转换入口Program.cs通常解析几个参数dotnet run -- --input ./data/config.xlsx --output ./out/config.json --sheet Sheet1参数含义--input是 Excel 文件路径--output是 JSON 输出路径--sheet指定 Sheet 名不传则用第一个。解析逻辑可以用System.CommandLine库也可以手写args遍历。手写的话大概是这样string input null, output null, sheet null; for (int i 0; i args.Length; i) { switch (args[i]) { case --input: input args[i]; break; case --output: output args[i]; break; case --sheet: sheet args[i]; break; } }这种写法简单直接适合小工具。如果参数多了建议换成System.CommandLine自带帮助信息和参数校验。4.3 批量导出遍历目录下所有 Excel实际项目里往往是一个目录下几十个 Excel 都要转批量模式很有必要public void BatchConvert(string inputDir, string outputDir) { var files Directory.GetFiles(inputDir, *.xlsx, SearchOption.AllDirectories); foreach (var file in files) { var relative Path.GetRelativePath(inputDir, file); var outPath Path.Combine(outputDir, Path.ChangeExtension(relative, .json)); var workbook OpenWorkbook(file); var sheet GetSheet(workbook, null); var data ParseSheet(sheet); var json ToJson(data); WriteToFile(json, outPath); } }逻辑说明递归查找所有.xlsx保持相对目录结构输出到目标目录。Path.ChangeExtension把扩展名换成.json。这样data/hero.xlsx会输出到out/data/hero.json目录层级不丢。提示批量模式下建议加个 try-catch单个文件出错不影响其他文件错误信息记到日志里。5. 避坑与常见问题那些让我加班到凌晨的细节5.1 空行导致 JSON 里多出空对象现象输出的 JSON 数组里出现{}空对象。原因Excel 里有些行看起来是空的但sheet.LastRowNum把它们算进去了GetRow返回非 null 但所有单元格都是空。解决在数据行循环里加判断如果整行所有单元格都为空就跳过bool isEmpty true; for (int c 0; c fields.Count; c) { if (row.GetCell(c) ! null !string.IsNullOrWhiteSpace(row.GetCell(c).ToString())) { isEmpty false; break; } } if (isEmpty) continue;5.2 数字被读成科学计数法现象长数字如订单号、身份证号在 JSON 里变成1.23456789012345E18。原因Excel 把长数字当数值存储NPOI 读出来是 double序列化时精度丢失。解决在 Excel 里把该列设成文本格式或者在ConvertCell里对数值类型做判断超过一定长度就转成字符串if (cell.CellType CellType.Numeric) { double val cell.NumericCellValue; // 超过 15 位有效数字转字符串 if (Math.Abs(val) 1e15) return val.ToString(F0); return val; }5.3 合并单元格只有第一个格子有值现象合并单元格对应的多行数据里只有第一行有值后面都是 null。原因Excel 合并单元格只在左上角存值其他格子是空的。解决读取时判断单元格是否属于合并区域如果是就取区域左上角的值private ICell GetMergedCell(ISheet sheet, int row, int col) { for (int i 0; i sheet.NumMergedRegions; i) { var region sheet.GetMergedRegion(i); if (region.IsInRegion(row, col)) return sheet.GetRow(region.FirstRow).GetCell(region.FirstColumn); } return sheet.GetRow(row)?.GetCell(col); }5.4 中文乱码现象输出的 JSON 文件用记事本打开中文是乱码。原因写入时用了默认编码可能是 GBK或者 JSON 序列化时转义了中文。解决写入用Encoding.UTF8序列化时设置UnsafeRelaxedJsonEscaping。如果已经生成了乱码文件用 VS Code 以 UTF-8 重新打开再保存。5.5 文件被占用无法覆盖现象第二次运行时提示「文件正被另一进程使用」。原因上一次读取的FileStream没释放或者 Excel 还开着这个文件。解决确保OpenWorkbook里的FileStream用了using或者手动Dispose。另外提醒策划改完表要关掉 Excel不然工具读不了。6. 二次开发加类型校验、换序列化库、接配置管线拿到源码后最值得改的三个方向类型校验、序列化库替换、接入自动化管线。加类型校验现在的转换逻辑对类型行是「信任」的如果 Excel 里写int但实际填了字符串转换会静默失败或输出错误类型。可以在ConvertCell里加一层校验类型不匹配时抛异常并记录行号列号private object ConvertWithValidation(ICell cell, string expectedType, int row, int col) { var value ConvertCell(cell, expectedType); if (expectedType int value is not int) throw new InvalidDataException($第 {row} 行第 {col} 列期望 int实际为 {value?.GetType().Name}); return value; }这样配置表出错时能第一时间定位而不是等到运行时才发现数据不对。换序列化库如果项目从 Newtonsoft.Json 迁移到 System.Text.Json注意几个差异日期格式默认不同、循环引用处理不同、Dictionarystring, object的序列化行为基本一致。迁移时先跑一遍对比测试确认输出 JSON 结构一致再切换。接入自动化管线把这个工具挂到 CI 里每次策划提交 Excel 后自动转 JSON 并提交到仓库。可以用 GitHub Actions 或 Jenkins命令行调用dotnet run -- --input ./configs --output ./output然后git add输出目录。这样程序拉到的永远是最新配置不用手动同步。验证方法改完之后拿一份包含各种边界情况的测试表跑一遍——空行、合并单元格、长数字、日期、公式、中文——对比输出 JSON 是否符合预期。我一般会准备一个test_cases.xlsx每次改完转换逻辑都跑一遍确认没回归。从那以后我每次改转换逻辑都强制先跑一遍边界测试表确认空行、合并单元格、长数字这三类都正常才提交。希望这个拆解能帮你少走点弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑