简介:一款面向开发者与数据分析人员的Excel转JSON实用工具,专注解决表格数据与Web/API常用JSON格式间的快速转换问题。资源包内含可直接运行的exe程序、运行所需的dll动态库以及基于C++的工程源码(含h/cpp文件),既可直接使用,也适合有二次开发需求的读者。压缩包共31个文件,整体约5.44MB,除可执行文件外,还附带说明txt、项目配置sln/vcxproj等,结构清晰便于查找。JSON凭借体积小、解析快、跨平台支持好等特性,在数据传输与接口对接中优势明显,借助该工具可省去手工编写转换脚本的繁琐,尤其适合需要批量处理Excel数据、或希望在本地高效完成格式转换的从业者。已有1018人学习下载,对提升数据处理效率有实际参考价值。
1. 先把“可交付”想清楚:excel转json 到底难在哪
Excel 转 JSON,单看需求是几行代码的事,但真正让人头疼的往往是把“脚本”变成“工具”的过程。数据在 Excel 里,接口要 JSON,写脚本的人自己跑得很顺,一旦把这一步交给运营或测试同事,对方可能没有 Python 环境、不熟悉命令行,甚至不想看到黑压压的报错窗口。于是,一个能双击运行的 exe 就成了这类协作里最省事的分发形态。这篇笔记不打算只给一段转换代码,而是按完整落地路径来讲:先确定 JSON 结构怎么定才符合业务,再写一个最小可用的转换器,最后用 PyInstaller 打包成 exe,同时把数据类型的坑和打包后的验证步骤也一起说清楚。适合需要定期处理 Excel 数据交付、想自己动手做一个可复用 excel转json 小工具的开发者。
2. 转出来的 JSON 长什么样:结构与类型策略
做 Excel 转 JSON 工具,第一步不是写代码,而是定输出结构。这是很多人翻车的地方:拿到需求就开始读表,结果交给后端时对方说“这没法直接用”。原因多半是结构没对齐接口预期,类型还和原始 Excel 感受不一致。这一章先说结构,再说类型推断,把这些确定下来,后面的代码才能一次写对。
2.1 行对象数组:默认优先级最高的 JSON 结构
接口文档没有特别要求时,我几乎总是用“行对象数组”,也就是 Excel 每一行变成一个对象,表头作为对象的 key,单元格作为 value。一个典型的输出长这样:
[ { "姓名": "A同学", "年龄": 23, "部门": "研发部" }, { "姓名": "B同事", "年龄": null, "部门": "测试部" } ]这个结构有三个好处:第一,它和pandas的to_dict(orient='records')天然对齐,实现成本最低;第二,对绝大多数业务配置和接口入参来说,每行一条记录是最直观的约定,后端拿到后不需要做二次拆分;第三,后续要转成 CSV、插入数据库或做数据对比,映射关系都不会丢。既然它默认满足大多数场景,工具就不需要在一开始支持一堆复杂配置。
什么时候别用它?当接口要求 JSON 对象本身按某个主键分组时,比如“按月份分组的所有订单”,或者数据里存在一对多关系,一个表头不够表达两级含义,这时候就要考虑嵌套结构。判断标准很简单:对方给的接口示例里,最外层是数组还是对象,数组元素里有没有再套对象。如果是后者,直接跳到下一节。
2.2 嵌套结构:点号表头约定与拆分实现
有些接口长这样:{"user": {"name": "A同学", "phone": "123"}, "address": {"city": "北京"}}。如果为每一层写一份映射规则,配置复杂度会成倍上涨,而且使用方还得理解映射关系。我常用的替代方案是“点号表头”:在 Excel 表头里直接写user.name、user.phone、address.city,一眼就能看出层级,转换程序只需要按点号拆分。
def nest_records(records): nested = [] for rec in records: out = {} for col, value in rec.items(): if isinstance(col, str) and "." in col: parts = col.split(".") node = out for part in parts[:-1]: node = node.setdefault(part, {}) node[parts[-1]] = value else: out[col] = value nested.append(out) return nestedsetdefault(part, {})的作用是逐层创建中间字典,parts[:-1]表示跳过最后一个字段名,只处理中间层级。这段代码逻辑不复杂,但约定本身要提前定死:如果 Excel 里已经有user这一列,就不能再有user.name,否则后者会把前者覆盖。实际模板里我还会限制嵌套不超过两层,三层以上的表头可读性太差,业务填表很容易填错位置。
嵌套模式还有一个容易被忽略的细节:空值处理。接口往往要求结构稳定,也就是user.mobile这个 key 必须存在,哪怕值是 null。嵌套实现里要保持“结构不变、值可以为空”,而不是遇到空值就省掉那一层。省掉后接口拿不到字段,又得回来排查,这是我在交付中最常遇到的一类返工。
2.3 类型推断是黑匣子:全字符串模式何时有用
Excel 单元格表面上是字符串,内部却有数字、日期、布尔、公式值等区别。pandas 读取时,只要一个数值列里出现空白,就可能把整列转成 float,JSON 里输出123.0;听起来不影响逻辑,但很多业务校验不认小数形式的“数量”。反过来,工号、编码、手机号这类带前导零的字段,如果 Excel 列被设成数字格式,读到 JSON 时前导零早已丢失。这类问题不是报错,而是“看起来成功、实际上不对”的静默错误,最需要在结构和策略层面提前约定。
我在类型策略上给两条路:默认保持原始推断,提供--string-columns参数让调用者指定列强制转字符串。转字符串的实现要小心,直接astype(str)会把空值变成字符串'nan':
def convert_string_columns(df, string_columns): for col in string_columns: if col in df.columns: mask = df[col].notna() df.loc[mask, col] = df.loc[mask, col].astype(str) return df先用mask标记非空行,再只对这些行做类型转换,空值保留 NaN,后续统一变成null。为什么不全表转字符串?因为转完所有数字都会变成带引号的字符串,后端如果期待 number 类型就会拒绝数据。所以“全字符串模式”不是默认选项,而是只对工号、编码、ID 这类特殊列启用。这个参数的意义在于,当你面对一个奇怪的数据模板时,不用改代码就能自救,算是给工具留了一颗后悔药。
3. 最小转换器:核心代码与命令行参数
结构方案定下来后,转换器本身可以非常精简。我把它分成读取、清洗、序列化三个阶段,用函数而不是顺序脚本,是为了后面打包 exe 时方便测试和按需扩展。这一章给的是可以直接复用的最小实现。
3.1 读取、清洗、序列化的三段式核心
import json import sys from pathlib import Path import pandas as pd def read_excel(path, sheet, header_row): df = pd.read_excel( path, sheet_name=sheet, header=header_row, dtype=object, ) return df def clean_empty(df, drop_all_empty=False): if drop_all_empty: df = df.dropna(how="all") return df.where(df.notna(), None) def write_json(records, output_path): with open(output_path, "w", encoding="utf-8") as f: json.dump(records, f, ensure_ascii=False, indent=2)dtype=object是关键参数之一,它告诉 pandas 不要提前做数值类型推断,尽量保留单元格原始对象,避免“明明没设置格式却被自动转类型”的问题。df.where(df.notna(), None)会把所有 NaN 值替换为 None,序列化后对应 JSON 的null。dropna(how='all')只删除整行全空的数据。ensure_ascii=False让中文以 UTF-8 原样写入文件,indent=2是为了人工审查时看得清楚;如果目标是给程序消费,可以把indent去掉,文件体积会小一些。
这三段式看起来基础,但它把“从哪读、怎么清、往哪写”拆开,后续加参数、加日志、加错误处理都钻到对应函数里改,不会滚成一团。接下来的 main 函数把这几个环节串起来。
3.2 参数设计:sheet、表头行、空值、日期
如果所有变化都写死在代码里,打包成 exe 后别人就没法按需调整。我建议把最常变化的维度做成命令行参数,既保留灵活性,也让工具可以用在批处理里。核心参数如下:
def build_parser(): parser = argparse.ArgumentParser(description="excel 转 json 工具") parser.add_argument("-i", "--input", required=True, help="输入文件 .xlsx/.xls") parser.add_argument("-o", "--output", default="out.json", help="输出文件,默认 out.json") parser.add_argument("-s", "--sheet", default=0, help="工作表名或索引,默认第一个") parser.add_argument("--header", type=int, default=0, help="表头所在行号,从 0 开始") parser.add_argument("--drop-empty-rows", action="store_true", help="删除整行全空的记录") parser.add_argument("--date-format", default="%Y-%m-%d", help="日期字段输出格式") parser.add_argument("--string-columns", nargs="*", help="强制按字符串输出的列名列表") return parser| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
-i/--input | 字符串 | 必填 | 输入文件路径,支持相对与绝对路径 |
-o/--output | 字符串 | out.json | 输出文件路径 |
-s/--sheet | 字符串或整数 | 0 | 传名字按名字取表,传 0 表示第一个表 |
--header | 整数 | 0 | 表头行索引,Excel 第 1 行是 0 |
--drop-empty-rows | 开关 | 关闭 | 针对 Excel 格式化残留空行 |
--date-format | 字符串 | %Y-%m-%d | 日期列输出格式 |
--string-columns | 列表 | 空 | 强制字符串输出的列名列表 |
--date-format容易有误解:如果某一列在 Excel 里本来就是文本,比如写着“2024/1/5”,pandas 读进来是字符串,format 不会做转换;只有被 pandas 识别成 datetime 类型的列才会按指定格式输出。看见很多工具的报错来自日期直接json.dump失败,因为 pandas 会把日期读成 Timestamp 对象,不能直接序列化,必须在写 JSON 前统一转成字符串。
3.3 命令行跑通第一个转换
把 main 函数补完整,让它能处理上面所有参数:
def normalize_dates(records, date_format): for rec in records: for key, value in rec.items(): if isinstance(value, pd.Timestamp): rec[key] = value.strftime(date_format) def main(): args = build_parser().parse_args() sys.stdout.reconfigure(encoding="utf-8") df = read_excel(args.input, args.sheet, args.header) df = clean_empty(df, args.drop_empty_rows) df = convert_string_columns(df, args.string_columns) records = df.to_dict(orient="records") normalize_dates(records, args.date_format) write_json(records, args.output) print(f"done: {args.output}")python excel_to_json.py -i 用户数据.xlsx -o users.json -s Sheet1 --drop-empty-rows --string-columns userIdsys.stdout.reconfigure(encoding="utf-8")是为了防止 Windows 控制台打印中文时出现乱码提示,不改也不影响 JSON 文件,但会影响使用者判断。命令行跑通后,可以先用正常数据生成一份完整 JSON,再用一条错误输入测试报错信息,比如指定一个不存在的文件:
python excel_to_json.py -i 不存在的文件.xlsx这个阶段把错误处理验证好,打包成 exe 后才能顺利排查问题。如果发现某个字段输出类型不对,优先按第 2 章的类型策略去检查 Excel 原始格式,而不是立刻改代码。
4. 打成 exe:PyInstaller 的 onefile 方案与验证清单
命令行好用不等于别人愿意用。交付给运营、测试、业务时,他们通常没有 Python 环境,也没兴趣维护依赖。把转换器打包成一个excel2json.exe,对方双击或简单带参数运行,对协作链路来说是最省摩擦的一步。现实里最多的坑集中在打包方式和依赖收集上,这一章说清楚我的默认方案。
4.1 为什么默认选 PyInstaller 而不是 Electron 或 Nuitka
市面上能做 exe 的方案不少,但定位不同。Electron 适合做界面复杂、跨平台、交互多的工具,缺点是打包体积动辄 200MB 起步,启动还要拉起整个运行时,对Excel转JSON这种小型工具太浪费。Nuitka 能把 Python 编译成 C 再生成可执行文件,理论上启动更快,但 pandas 这类库在 Nuitka 下编译速度很慢,报错也更难排查,维护成本高。PyInstaller 的定位是“把解释器和依赖原样打进包”,在 Windows 下社区最成熟,遇到问题基本都能查到明确结论,所以我一般无脑选它。
需要提前知道的代价:PyInstaller 不是真正编译,脚本逻辑仍然可以被还原。如果转换规则里有业务机密,比如复杂的计价公式,打包成 exe 不代表安全。对内部工具这通常不是问题,但对外分发前要想清楚这个边界。另外一个代价是体积,pandas 全家桶打进去后 exe 大概率超过 50MB,接受这一点,不要因为体积去折腾各种裁剪优化,省下的空间换不回维护时间。
4.2 打包命令与两个容易忽略的细节
先安装依赖,再执行打包:
pip install pyinstaller pandas openpyxl pyinstaller --onefile --console --name excel2json excel_to_json.py--onefile生成单个 exe,分发最方便;--console保留命令行窗口,方便使用者看到提示和报错。如果做图形界面,可以改成--noconsole,但排障会难很多,建议第一版先保留控制台。命令执行后会在当前目录生成dist/excel2json.exe,build/目录存放中间文件,可删。
一个容易踩的细节是 openpyxl 依赖收集不全。pandas 的依赖关系比较复杂,PyInstaller 大部分情况能自动识别,但 openpyxl 偶尔在用户机器上报 module not found。稳妥做法是打包时显式收集:
pyinstaller --onefile --console --name excel2json --collect-all openpyxl excel_to_json.py这会稍微增大体积,但能避免“这台机器报错、那台机器正常”的玄学问题。另一个细节是构建时用--noconfirm --clean清掉上一次的缓存,避免旧版本被打进去:
pyinstaller --noconfirm --clean --onefile --name excel2json excel_to_json.py每次改完代码重打,我都会加--clean。这个习惯帮我省掉好几次“为什么改了没生效”的排查时间,属于血泪经验。
4.3 分发前的验证清单:别只在能跑的机器上试
打包完第一件事不是发给别人,而是自己按“陌生环境”来测试。我的验证清单有四项:第一,找一台没有 Python 的机器或虚拟机跑一次,能过才说明依赖没漏;第二,把输入文件放到带空格和中文的路径里,比如C:\Users\某同事\Desktop\数据\输入表.xlsx,确认路径解析正常;第三,用文本编辑器打开生成的 JSON,确认中文没有乱码、日期格式正确;第四,如果是给别人双击使用,先手动在终端里跑一次,不要用双击方式去排错,否则窗口一闪而过什么都看不到。
我还遇到过输入文件被 Office 占用导致读取失败的情况。Excel 打开同一份文件时会加锁,转换程序读不到内容。工具层面能做的只有给出清晰报错,判断逻辑按顺序来:先看路径是否存在,再看文件是否被占用,最后才是格式异常。代码里可以先加一条前置检查:
if not Path(args.input).exists(): raise SystemExit("输入文件不存在,请检查路径")这种提示比 pandas 底层的英文报错友好得多。内部工具的使用者不是程序员,真正触发问题时,他们只能把看到的文案原样转述给你,所以中文提示本身就是第一道排查工具。打包前把这类前置检查加全,后续维护会省心很多。
5. 避坑与排查:数据看着没错,JSON 却不对
这一章记录我在维护这类工具时遇到最多的五类问题,每一类都按“现象、原因、解决”来写。它们大多不是语法错误,而是数据和环境的隐形差异引起的,排查思路比语法问题更值钱。
5.1 现象:整列数字在 JSON 里变成了字符串
现象:Excel 里一列明明是数字,输出的 JSON 里却带着引号。原因:该列在 Excel 中被设置了文本格式,pandas 按 object 读取后保留成字符串;也可能是单元格里有空格或换行符,Excel 自动按文本处理。解决:先看原始 Excel 的列格式。如果目标本身是 JSON 数字,把 Excel 单元格格式改成“常规”或“数字”,清掉不可见字符再转换;如果这类编号本来就该是字符串,比如工号带前导零,源单元格就必须存成文本,否则任何工具都无法还原“00123”这个值。这个问题必须在源头解决,工具层面加多少参数都补不回来。
5.2 现象:.xls 老版本文件在打包后报错无法读取
现象:同样内容,.xlsx正常,.xls报错,有时提示缺少某个模块。原因:pandas 读取.xls依赖 xlrd 库,而 xlrd 高版本只支持.xls格式,打包时如果没有正确收集相关依赖,就会出现部分机器能读、部分不能读。解决:我的习惯是只分发.xlsx,用 Excel 或 LibreOffice 把.xls另存为.xlsx。如果业务上必须支持.xls,打包时显式加--collect-all xlrd,并在验证清单里专门准备一份.xls测试文件。别在图方便把所有格式都打包进去,能收窄支持的格式就收窄,体积和坑都会少一些。
5.3 现象:表头是合并单元格,JSON 里出现Unnamed: 1
现象:输出对象的 key 里出现Unnamed: 1,而且对应值明显错位。原因:pandas 把指定行当表头时,合并单元格只有左上角有值,其余位置是 NaN,于是列名变成Unnamed: n。解决:最直接的办法是要求 Excel 模板取消合并,每列表头都填完整。如果模板改不了,可以在工具里用df.columns = df.columns.to_series().ffill()做向前填充,但只解决了列名缺失问题,数据位置的正确性仍需人工确认。另一种变体是两列同名,比如两个“备注”,JSON 里会出现重复 key 且后者覆盖前者。这个错得不报,但数据已经丢了。工具里最好加一道检查,如果列名有重复,直接报错提示改表头,不要默默生成不完整数据。
5.4 现象:表尾空行被输出成大量 null 对象
现象:JSON 数组末尾出现十几个类似{"姓名": null, "数量": null}的对象。原因:Excel 工作表里看似为空的区域,实际可能带了背景色、边框或格式标记,pandas 默认把这些范围读进来了。解决:命令行加--drop-empty-rows,内部用dropna(how='all')删除整行全空的数据。注意how='all'只处理整行全空,如果某些单元格里是空格字符,它不会被当成空行,需要先在 Excel 里清理单元格内容,或者在工具里加一步去除首尾空格的预处理。这类问题在开发机上不一定出现,因为测试数据往往干净,到了真实模板才露馅,所以验证阶段就要用真实模板。
5.5 现象:打包后的 exe 被安全软件拦截或运行时闪退
现象:exe 在开发机跑得好好的,拷到别的机器要么被安全软件拦截,要么双击后没反应。原因:PyInstaller 的 onefile 模式启动时会解压到临时目录再运行,这个行为容易被安全软件判断为风险操作;另一类闪退是目标机器缺少 VC++ 运行库。解决:对内部工具,把 exe 所在目录加入安全软件白名单,或者改用--onedir模式,生成一个文件夹分发,启动更稳定,误报率也低。对外发布到不固定环境时,还需要提前装好 VC++ 运行库或把它们一起分发。这个问题不是脚本 bug,不要在代码里浪费时间,优先从打包模式和运行环境两个方向查。
6. 进阶:让 exe 进自动化流水线并验证输出
上面已经把工具做到能打包、能分发,但离“可信的日常工具”还差一步:验证。没有验证的转换工具,数据一上百行就不敢直接对接接口。最后给两个习惯:用 jq 检查结构,用退出码与批处理衔接。
6.1 用 jq 做转换后的结构验证
拿到 JSON 后不要急着看内容,先跑三条命令。jq '.[0]' out.json看第一条记录的完整形状;jq '[.[] | select(.userId == null)] | length' out.json统计空字段,快速找出缺失值;jq 'map(keys) | flatten | unique' out.json列出全部唯一 key 集合,对比多个数据文件的 schema 是否一致。如果 key 集合比模板少了某个键,多半是表头变了或合并单元格导致列被丢弃。把这组命令固定成验证步骤,每次转换后跑一遍,比肉眼检查可靠得多。
6.2 退出码约定与批处理衔接
为了让其他程序或定时任务能判断“这次转换是否成功”,我在工具里约定退出码:0 表示成功;1 表示参数错误;2 表示输入文件不存在;3 表示转换过程异常。批处理脚本可以这样写:
excel2json.exe -i 报表.xlsx -o out.json --drop-empty-rows if %ERRORLEVEL% neq 0 exit /b %ERRORLEVEL%这样转换失败时,下游任务不会把半成品 JSON 继续往后传。实现时在 main 里用sys.exit(2)明确退出,而不是让代码自然崩溃。自然崩溃的报错对使用者没意义,退出码反而是最直接的结果信号。
6.3 收尾习惯:给参数一套固定模板
我还有个习惯:每次把转换工具交给别人,都会顺手提供一个写好的命令示例,让对方只换路径即可。比如excel2json.exe -i 配置.xlsx -o config.json --drop-empty-rows --string-columns id这样一个固定模板,能省掉大量“参数怎么填”的沟通。工具好不好用,很多时候不取决于功能多少,而是输入约定有没有固定下来。模板跑顺之后,参数、输出路径和字段规则会慢慢沉淀成团队内部规范,甚至可以直接挂进定时任务,不再需要人工介入。维护这类工具越久,我越觉得“少写代码、多定规范”才是真正少踩坑的办法。
希望帮到你。
本文还有配套的精品资源,点击获取