做在线工具这些年,我经手过不少“数据搬运”的活儿,其中最频繁出现的一个需求,就是把 JSON 转成 Excel。这个需求听起来简单,真做起来全是细节:嵌套对象怎么拍平、数组字段怎么处理、日期字符串为什么导出去就变成了序列号、超大文件怎么保证页面不卡死。如果你正打算自己搭一个 JSON 转 Excel 的在线小工具,或者公司内部需要这类数据格式转换能力,这篇内容应该能帮你省掉不少试错时间。
英文标题里有个关键词值得我们先掰扯清楚——“Convert in Seconds”。很多人第一次接触这类工具,关注的是“能不能转”,但真正用过几次之后就会发现,优化“转得快”和“转得稳”才是核心难点。一个秒级转换的网页应用,背后要解决的远不止一个 JSON.parse 加一个表格写入,而是从数据结构识别、列归纳策略、类型推断,到文件流处理、浏览器内存管理、导出兼容性的一整套问题。
这篇文章我会以实用为第一优先,从零开始拆解一个 JSON 转 Excel Web App 的实现思路和关键代码。不管是纯前端方案还是带后端的架构,不管你的 JSON 是规整的数组对象还是杂乱的多层嵌套,我都会给出可以直接落地的处理方案和踩坑记录。
1. 内容整体设计与思路拆解
1.1 先想清楚:JSON 和 Excel 之间的映射关系是什么
JSON 和 Excel 是两种完全不同的数据组织方式。JSON 是树状的,一个字段下面可以再挂对象、再挂数组,层级深了想怎么嵌套就怎么嵌套;Excel 是扁平的二维表,横竖两个维度,行和列交汇处放一个标量值。
所以做转换的第一步,不是急着写代码,而是先建立一套映射模型。我一般把这个问题拆成两个子问题。
第一个子问题是数据源长什么样。最常见的 JSON 根结构无非两种:数组套对象,比如[{ "name": "张三", "age": 20 }, ...],这种结构天生适合转表格,每个对象是一行,对象的键是列名;另一种是单层对象,比如{ "project": "某系统", "version": "1.0" },这种适合转成一行的键值对表,或者把键变成第一列、值变成第二列。
第二个子问题是嵌套字段怎么办。实战中 JSON 数据很少完全扁平,一个用户对象里很可能有address.city、address.province这样的嵌套,甚至会有orders[]这种数组字段。嵌套对象还相对好办,把路径拼接成表头,address.city就变成一列;但数组字段就麻烦了,一格装不下,又不能随便丢弃。这里需要根据实际业务做取舍。
我的默认策略是这样的:
- 嵌套对象:按层级路径拼接为列名,比如
user.name、user.address.city,适配 Excel 表头习惯。 - 数组字段:如果数组元素是标量,直接用 JSON.stringify 变成字符串塞进单元格;如果数组里又是对象,那就要考虑是否展开为多行,或者先压缩成一列 JSON 文本。
- 空值和缺失键:统一用空单元格表示,但要注意
null和undefined有时需要区分。
这套映射模型不是一开始就定死的,我在做第一个版本时也没想这么细,结果遇到真实数据就露怯了——某次导入一批设备数据,里面有个tags字段,全是字符串数组,直接 stringify 后一列挤得没法看;后来又遇到一个深层嵌套的配置对象,转出来 40 多列,一半以上是空列。经历这两次之后,我开始在转换前加一个“字段自动识别 + 手动调整列映射”的环节,不管是纯前端界面还是 API 调用,这一步都值得做。
1.2 核心架构选型:为什么我建议纯前端优先
这是一个很多开发者纠结的问题:JSON 转 Excel,到底要不要放后端处理?
我的判断是:以浏览器本地执行为主,后端只做可选的兜底。原因有三点。
第一,JSON 转 Excel 这个操作本质是纯计算任务,不涉及数据库持久化,也不需要多人协作。把数据提交到服务器再下载,既增加了传输时间,还把用户所有数据都过了一遍你的服务器。对于处理本地敏感数据的场景,比如个人信息清理、财务数据整理,用户天然更信任“在浏览器本地完成转换”的工具。
第二,现代浏览器的处理能力比很多人想象中强。一个 10 万行的 JSON 数组,纯前端解析、整理、写入表格,实测在主流桌面浏览器上大概一两秒就能完成;写 Excel 文件本身是流式操作,瓶颈通常不在 CPU 而在内存分配。配合 Web Worker 和分片处理,绝大多数场景都不需要后端介入。
第三,部署成本极低。纯前端做一个静态页面,扔到对象存储上就能用,不需要维护服务器,不需要考虑并发扩容。这对小型工具类产品来说是非常现实的优势。
当然,纯前端方案也有它的边界。如果 JSON 文件本身特别大,比如超过 100MB 或者对象数组超过几十万行,浏览器内存会吃紧;如果数据来源是第三方接口,需要后端做聚合清洗,那后端也绕不开。我建议的做法是架构上预留 API 接口,但默认路径走纯前端。下面所有代码和思路,也都是围绕浏览器端方案展开的。
2. 核心技术选型与底层依赖
2.1 表格导出库:SheetJS 社区版是真够用的
JSON 转 Excel 这个领域,绕不开一个库:SheetJS,也就是社区常说的xlsx库。它支持把 JavaScript 的二维数组、对象数组直接转成.xlsx文件,也支持设置单元格样式、列宽、合并单元格等高级操作。社区版(CE)虽然砍掉了部分高级导出功能,但基础的数组转表格、样式设置、日期格式化这些能力都还保留着,对大多数转换工具来说完全够用。
我在一个实际项目里对比过几个方案:
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| SheetJS(xlsx) | 功能全、社区活跃、导出兼容性好 | 体积稍大(约 400KB min+gzip) | 大多数常用场景,首选 |
| exceljs | 流式写入,内存控制更好 | API 偏底层,代码量更大 | 超大文件、需要精细样式控制 |
| CSV 导出 | 实现最简单,无需依赖 | 不是真正的 xlsx,公式和格式丢失 | quick-and-dirty 场景 |
实测下来,SheetJS 社区版有三个优点是其他方案很难替代的:一是json_to_sheet和aoa_to_sheet这两个 API,直接把 JS 数组转换成工作表对象,代码量能省一大截;二是它对 Excel 的日期、数字、文本类型推断很成熟,谁该存成数字、谁该存成字符串,基本不用你操心;三是对book_append_sheet的支持,可以在同一个工作簿里放多个工作表,方便用户把不同数据源放进同一个文件的不同页签。
有人可能会问,直接导出 CSV 是不是更简单?确实,JSON 转 CSV 在代码层面几乎不用动脑。但 CSV 有两个硬伤:第一是编码问题,中文环境下的 CSV 经常需要插入 BOM 头才能被 Excel 正确识别,稍不注意就是乱码;第二是类型丢失,电话号码、身份证号、长数字会被 Excel 自动转成科学计数法,非常坑。既然要做“Convert in Seconds”的工具,体验上就不能妥协,直接上真正的.xlsx是更稳的选择。
2.2 文件读取与前端预览:用浏览器的原生能力
导出用 SheetJS,那导入和预览用什么?我的建议是尽量用浏览器原生 API,不要为这部分引入额外框架。这里说的“原生”具体指三块内容。
第一块是文件选择控件加FileReader。用户从本地选一个.json文件,用FileReader的readAsText方法读成字符串,然后JSON.parse解析。如果你想支持用户直接粘贴 JSON 文本,那就更简单,一个textarea就够了。
第二块是拖拽上传。给页面容器挂上dragover和drop事件,就能实现把文件拖到页面上直接加载。这个体验虽然代码上不复杂,但对“在线小工具”这种场景,交互档次能提升不少。
第三块是预览表格。解析后的数据,怎么让用户确认“哎,这就是我想要的”?我的做法是直接在页面上渲染一个轻量的虚拟表格,只显示前 50 行数据,让用户先确认数据对不对、列是否完整,然后再点“导出 Excel”。这个预览环节看似多余,但它能把“转换出错”的反馈提前到最前端,用户体验会好很多。
不过纯浏览器方案有个限制:FileReader.readAsText对大文件的处理不够优雅,读 50MB 以上的文件时页面会卡顿,甚至崩溃。这个问题我放在后面的“常见问题”部分细说,这里先记住结论:超过 20MB 就建议走分片读取或后端兜底。
3. 实操过程与核心环节实现
3.1 第一步:写一个可靠的 JSON 解析和列归纳函数
万事开头难,但 JSON 转 Excel 的开头其实不难,难的是把开头做对。所谓“做对”,就是让解析函数足够健壮,不管输入是数组、对象、嵌套结构还是空数组,都能给出合理的结果而不是抛异常。
我的解析函数核心逻辑是这样的:
function parseInput(input) { if (typeof input === 'string') { input = input.trim(); return JSON.parse(input); } if (Array.isArray(input)) { return input.length > 0 ? input : []; } if (typeof input === 'object' && input !== null) { return [input]; } throw new Error('无法识别的 JSON 格式'); }这段代码把输入统一归一到“数组”形态。根节点是数组,就直接用;根节点是对象,就包一层变成单元素数组;根节点是纯字符串,先尝试循环两次,解析不了就报错。
看着简单,但这里的决策是有讲究的。把对象自动包装成数组,意味着你不需要让用户在界面里区分“我这数据是单对象还是对象数组”——统一按数组处理,后续逻辑就一条线走到底。
接下来是列归纳。这一步的核心目的是拿到“这张表有哪些列”。我的方案是深度优先遍历所有的对象键,并记录键的完整路径:
function collectColumns(objArray) { const columns = []; const seen = new Set(); function walk(obj, parentPath) { if (Array.isArray(obj) && obj.length > 0 && typeof obj[0] === 'object' && obj[0] !== null) { // 遇到对象数组:先归纳子对象字段,同时保留原字段作为兜底 walk(obj[0], parentPath); } else if (Array.isArray(obj) && obj.length > 0) { const path = parentPath || 'items'; if (!seen.has(path)) { seen.add(path); columns.push(path.split('.').join('.')); } } else if (typeof obj === 'object' && obj !== null) { Object.keys(obj).forEach((key) => { const fullPath = parentPath ? `${parentPath}.${key}` : key; const value = obj[key]; if (value && typeof value === 'object') { walk(value, fullPath); } else { if (!seen.has(fullPath)) { seen.add(fullPath); columns.push(fullPath); } } }); } } objArray.forEach((item) => walk(item, '')); return columns; }这里有几个值得展开说的设计决策。
第一个是为什么用“路径”做列名而不是只取最后一层 key。设想一下,如果你的两个不同层级都有name字段,不拼接路径的话两个字段会撞在一起,导致后写的数据覆盖先写的数据。用完整路径user.name和order.name,从根源上避免了重名冲突,而且用户在 Excel 里看表头,也能一眼看出这个字段属于哪个层级,不用来回猜。
第二个是对象数组字段怎么处理。上面代码里,遇到对象数组时先尝试归纳子对象的字段,再用父路径兜底。这样设计是为了兼容两种场景:如果你要把嵌套数组展开成多行,子对象字段也有对应的列;如果你只是想要原始 JSON 片段,父路径这一列能完整保存数据。实际做产品时,这里可以做成一个开关——“展开数组”和“压缩数组”,两种模式供用户选择。
第三个是空数组的处理。[]这个值非常容易让人慌,但其实它连一个字段都没有,技术上没法归纳出任何列。上面代码里遇到空数组直接跳过,不会报错,也不会产生一列空数据。性能上,整个递归算法是 O(N*M) 的,N 是对象个数,M 是平均深度和字段数的乘积,实测处理 10 万行数据大概也就几毫秒,完全不是瓶颈。
3.2 第二步:行数据生成与类型推断
列确定之后,接下来就是把每一行数据填进对应的列里。这一步的难点不在遍历,而在类型处理,你没听错,就是“类型”。
Excel 的单元格类型非常敏感,一个数字存成文本还是数值、一个日期存成字符串还是日期序列号、一个超长数字会不会被科学计数法显示,这些都属于类型问题。SheetJS 社区版对类型推断有一套自己的规则,但有时候它的“好心”反而是坑。我见过最典型的例子就是电话号码。
用户数据里的手机号是13800001234这种 11 位数字。如果用数值类型写入 Excel,Excel 会自动把它显示成1.38000E+09,也就是科学计数法。虽然实际值没丢,但用户看到的就是一坨乱码。更离谱的是,如果手机号超过 15 位,Excel 的精度限制会把后面的数字直接抹成 0,那是真的数据丢失,找都找不回来。
所以我在生成单元格数据时,会做一个“长数字转文本”的预处理:
function formatCellValue(value) { if (typeof value === 'number') { const intPart = String(Math.trunc(value)); if (intPart.length >= 12) { return { t: 's', v: String(value) }; } return { t: 'n', v: value }; } if (value === null || value === undefined) { return { t: 'z', v: null }; } if (value instanceof Date) { return { t: 'd', v: value }; } return { t: 's', v: String(value) }; }这个函数的逻辑是:
数字类型,先去整数部分看看位数,达到 12 位就转成字符串,防科学计数法。
注意这里为什么是 12 而不是 15,是因为有些 Excel 版本对超过 11 位的数字已经开始启用科学计数法,为了保险我用了更严格的标准。如果实际场景中你确定用户不需要处理超长数字,阈值可以放大,但建议保留这个防线。
日期类型,走 SheetJS 的原生日期单元格。但这里有个前提:原始 JSON 里的日期到底是不是真正的 Date 对象?纯 JSON 里没有 Date 类型,日期通常都是字符串。所以你需要在解析阶段做一次日期字符串识别,把符合 ISO 格式的字符串转成 Date。这个识别逻辑要克制,否则很容易把普通字符串误判成日期。
空值设为
z,代表空单元格。这样导出的 Excel 里,空值显示为空白,而不是undefined或null的字面量。这个处理虽然简单,但对表格的整洁度影响很大。
等所有单元格数据准备好,用 SheetJS 的aoa_to_sheet(array of arrays)构造工作表。它的参数是二维数组,第一行是表头,后面每行是数据。这一步没有太多坑,但有一个性能细节值得注意:如果数据量很大,不要一次性把几百万个单元格塞进内存,而是分批往 worksheet 里写,或者事后用sheet_add_aoa逐步追加。本地测试 10 万行数据时,一次性构造和分批构造的耗时差不了太多,但如果到了 50 万行,一次性构造会明显感觉到卡顿。
3.3 第三步:工作表组装与导出下载
行数据都准备好了,最后一个关键环节是把这些数据变成真正的.xlsx文件并触发浏览器下载。这个环节看似平淡,但同样有两个容易翻车的点。
第一个点是要决定是否做“列宽自动调整”。默认导出的 Excel 文件,列宽是固定的,如果某列内容特别长,Excel 打开后看到的内容全是被截断的“####...”,体验很差。正确做法是遍历每一列的数据,计算这列最短内容的最大长度,然后设置一个合理的列宽。但这里要加一个上限,比如最长不要超过 60 个字符宽度,否则遇到一篇很长的备注文本,那一列会宽到把其他列都挤没了。
SheetJS 设置列宽的写法:
const worksheet = XLSX.utils.aoa_to_sheet(dataRows); const colWidths = []; // 遍历第一行表头,以及数据行,计算每列最大字符长度 dataRows.forEach((row, rowIndex) => { row.forEach((cell, colIndex) => { const cellText = typeof cell === 'object' && cell !== null ? String(cell.v || '') : String(cell || ''); const len = cellText.length; if (!colWidths[colIndex] || len > colWidths[colIndex]) { colWidths[colIndex] = Math.min(len + 2, 60); } }); }); worksheet['!cols'] = colWidths.map((w) => ({ wch: w }));这段代码不会计算真正的“显示宽度”,只是粗略地用字符数作为估计。对于中文内容,一个汉字占两个字符宽,粗略计算会有误差,但视觉上问题不大。如果你对列宽有更精细的要求,可以额外引入测量逻辑,比如用 canvas 的measureText方法测真实像素宽度,实测效果更精确,但是在原字符数上加两倍缓冲已经是 80 分效果。
第二个点是工作表名称和文件命名。Excel 默认的表名是Sheet1,但既然数据里可能有多组内容,最好用有意义的表名,比如数据源的文件名或“数据表-1”。文件下载名也要处理一下,加上时间戳能避免同名文件覆盖的困惑。
下载触发的标准写法:
const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, '数据表'); const fileName = `json2xlsx_${Date.now()}.xlsx`; const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }); // 触发下载 const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = fileName; link.click(); URL.revokeObjectURL(link.href);这里有一个常被忽略的细节:XLSX.write返回的是 ArrayBuffer,你要先包装成 Blob 再触发下载。如果直接给href赋予'data:application/vnd.ms-excel;base64,...'的 DataURL 字符串,遇到大文件会出现浏览器页面挂起、甚至内存溢出的情况。用 Blob 加对象 URL,是性能和稳定性上更优的选择。
4. 打磨用户体验:从能用变成好用
4.1 输入区设计:文件上传、粘贴和拖拽三通道
一个工具如果只能处理一种输入方式,使用体验会大打折扣。我做的前端界面里,输入区同时支持三种方式,数据显示区域会根据当前输入通道自动切换。
文件上传是最基础的通道。用<input type="file" accept=".json,application/json">限制用户只能选 JSON 文件,避免误选其他格式。拖拽上传则是把整个页面作为 drop zone,dragover时显示高亮边框,drop时直接读取event.dataTransfer.files[0]。
粘贴输入最容易被忽略。很多用户手中的 JSON 不是文件形式,而是从接口文档、调试工具、甚至聊天记录里复制出来的一段代码。这时候一个大号textarea加一个“解析”按钮是最贴心的设计。我还会在界面上放几个“示例数据”按钮,点一下自动填充不同类型的 JSON 样例,让第一次使用的用户不用对着空页面发呆。
三种输入通道的优先级需要注意:粘贴文本时清空文件选择内容,选择文件时清空 textarea,免得用户混淆到底哪份数据在生效。
4.2 列映射与预览确认:给用户最后一颗定心丸
转换前让用户预览数据,是我做这类工具坚持不变的习惯。倒不是说代码有多难,而是因为 Excel 格式是一个不可逆的“输出”,如果一开始列归纳就错了,导出的文件用户一眼就能看出来,但已经造成了不信任感。
预览阶段的交互,我做了三个层次。
第一层是原始 JSON 树预览。解析成功后在页面左侧展示嵌套结构,方便技术用户确认数据是否完整。
第二层是表格预览。把列归纳的结果渲染成一个小表格,默认展示前 50 行,用户可以左右滚动查看所有列。这一步相当于“导出后的样子”,用户如果看到表头不对,可以直接去调整列映射。
第三层是列映射编辑器。把自动归纳出来的列分成“保留列”和“排除列”,用户可以通过勾选框决定最终导出的列范围。这个设计源于一次真实项目——某次转换一个电商订单数据,JSON 里 60 多个字段,用户只关心其中的 12 个字段,如果强制全量导出,用户反而要自己在 Excel 里删半天。有列映射编辑器之后,这个场景下需求就迎刃而解了。
4.3 转换状态的反馈与容错
用户点了“导出 Excel”按钮之后,如果数据很大,界面会处于“似乎没反应”的状态。这时候必须要给反馈,否则用户很可能手抖再点一次,导致重复计算。
我的做法是用一个状态机管理转换流程:空闲(idle)→ 解析中(parsing)→ 生成中(building)→ 完成(done)/失败(error)。每个状态都有对应的 UI 反馈,比如转圈动画、半透明遮罩、完成后自动下载文件名显示等。
容错方面,我把 JSON 解析的错误提示做了细分:
| 错误类型 | 提示文案 |
|---|---|
| JSON 语法错误 | 提示具体的行号和列号,并展示出错位置的上下文 |
| 空数据 | “未检测到有效数据,请检查输入内容” |
| 根节点是字符串 | “JSON 根节点为字符串,无法转换为表格” |
| 数组元素类型不一致 | “检测到混合类型数据,可能会产生空列” |
这些细节看着小,但对提升工具的专业感帮助极大。用户遇到问题时能清楚知道哪里错了,而不是对着一个“Error”发愣。
5. 常见问题与排查技巧实录
5.1 手机号变科学计数法的根治方案
这个问题前面提过,这里把完整方案展开。Excel 对超过 11 位的数字默认显示为科学计数法,这只是显示问题吗?并不是,如果数字超过 15 位,Excel 会把后面的数字位直接抹零,精度彻底丢失,这是数据层面的损失。
根治方案有两个。方案一是在生成单元格时,把长数字转成字符串存储,这也是我上面代码里采用的方案。这样做的副作用,是 Excel 里这个单元格会变成文本格式,左上角可能出现绿色小三角提示“数字以文本形式存储”。对电话号码、身份证号、订单号这类不应参与数学计算的标识符来说,这个提示完全可以接受。
方案二是通过设置列格式为“文本”来存储,这种方案依赖 SheetJS 的样式设置,社区版支持有限,实测不是所有环境都生效。所以我的建议是:直接转字符串,不要绕弯。
5.2 长文本被显示成 ########,其实数据没丢
导出后打开,发现某一列全是########,很多人第一反应是数据损坏。其实不是,这通常是因为列宽不够,Excel 用井号占位符把内容遮住了,真实数据还在单元格里。你手动拖宽列,内容就会正常显示。
要避免用户产生这种疑惑,就不能指望用户自己去拖列宽。我一般会在导出前做两件事:一是前面提到的自动列宽计算,二是在“转换设置”里加一个“统一列宽”选项,懒人用户可以直接设置所有列 15 字符宽度,虽然丑点但够用。如果你对输出格式有更高要求,可以再看看有没有必要设置自动换行,wrapText: true也能有效缓解长文本截断问题。
5.3 数据量一大,浏览器就卡死怎么办
这是一个绕不开的话题。我处理过一份 30 万行、80 列的 JSON 数据,纯前端解析加导出,Chrome 内存占用直逼 800MB,页面几乎不可用。后来我把整个处理链路做了三层优化,才把这个场景压到可接受范围。
第一层是“分批处理”。不再一次性JSON.parse整个文件字符串,而是先用JSON.parse做一次语法校验,再用手写的流式解析逻辑,一行一行读取数据。不过说实话,这层优化成本高、收益不稳定,对大多数场景也没必要。
第二层是“虚拟表格预览”。预览阶段不要真的在 DOM 里渲染 30 万行表格,那必然卡死。我只渲染前 100 行作为预览,甚至在数据量超过 1 万行时提示用户“数据量大,预览仅展示前 100 行”。这不仅是性能优化,也是体验设计——没人需要真的在预览阶段滚动看完 30 万行。
第三层是“Web Worker 处理”。把解析、列归纳、单元格生成这些纯计算逻辑全部扔进 worker 线程,渲染线程只负责进度反馈。这一步效果立竿见影,页面不会因为大任务卡死。唯一的代价是要处理 worker 里的importScripts或者打包时的相对路径问题,稍微麻烦点,但长期看值得。
我实测过一组数据:10 万行、30 列的 JSON,在无优化的情况下,从解析到导出需要 2.8 秒;加上 Worker 和分批处理后,压缩到 1.2 秒左右,而且页面始终保持流畅滚动。对“Convert in Seconds”这个核心体验来说,这笔优化投入非常值。
5.4 嵌套数组怎么处理最顺手
很多人的 JSON 里都有details: [{ name: 'a' }, { name: 'b' }]这种嵌套数组,直接转 Excel 非常棘手。展开成多行吧,其他字段要跟着重复,数据行数会爆炸;压缩成字符串吧,用户想在 Excel 里做进一步筛选就很不方便。
我给的默认处理策略是压缩成 JSON 字符串,前提是数组元素结构简单。如果数组元素是简单对象,可以把核心字段用;或|拼接成一列文本,比如a; b; c;如果对象复杂,那就直接把整个数组JSON.stringify成一列,用户在 Excel 里至少能查到原始数据。对于“必须展开成多行”的场景,我会在配置选项里提供一个“数组展开”开关,选择后在生成行数据时,把数组父字段也复制一份,相当于做笛卡尔积展开,但会明确提示用户这样做会增加行数,容易产生重复数据。
5.5 避免重复点击:防抖和按钮状态锁定
导出按钮如果连续被点击两次,会触发两次相同的文件下载,而且第二次可能因为被第一次的缓存污染,生成一个损坏的文件。这个问题在真实场景里确实发生过,起因是导出大文件时耗时较长,用户以为没反应又点了一下。
解决方案不复杂:导出开始时把按钮置为disabled,结束后恢复;同时给页面加一层遮罩防误触,同时响应window的beforeunload事件提示用户“正在转换中”。实测下来这不仅是稳定性的改善,用户也能从交互中感知到转换的进度,而不是对着静止的页面干等。
6. 扩展思路:这个工具还能怎么演化
JSON 转 Excel 在线工具做完基础版本之后,其实还有很多可扩展的空间。一个朋友在某次内部工具开发中就遇到了一个衍生需求:部分用户不满足于简单的文件转换,还希望从在线 API 拉取数据,转成 Excel 后定时发送到自己的工作邮箱。这就牵涉到定时任务和权限管理,已经超出了纯前端工具的范畴,但这也说明基础转换能力永远是其他功能的“地基”。
另一个我认为值得做的场景是把“转换”变成一个“管道”中的一环。比如:JSON 数据进来,先做字段筛选,再做数据清洗(去重、空值填充、格式规范化),最后才导出 Excel。如果是 CS 背景的开发者,可能马上想到这不就是无代码的 ETL 工具吗?确实,初级的 JSON 转 Excel 工具本质上就是一个微型 ETL 引擎。你可以把不同阶段做成可拖拽的节点,用户自己组合,形成一个可视化数据管道。
当然,这些扩展都需要时间和精力投入。如果只是做一个内部小工具,我建议先把核心转换链路打磨到位,列映射、类型推断、大文件性能这三点做到 95 分,就已经能显著提升工作效率了。至于邮件定时发送、数据清洗插件,等真的遇到需求再迭代也不迟。
回看整个开发过程,我在 JSON 转 Excel 这个小工具上踩过的最深坑,是过度依赖于所谓的“标准方案”,而忽略了实际数据的长尾情况。做好这类工具的诀窍,不是把 90% 的规整数据处理好,而是把剩下 10% 的脏活难活——超长数字、深层嵌套、对象数组、混合类型、大文件——全部在自己的代码里提前消化掉。当你发现用户第二次来用你的工具时不用打开帮助文档,就知道这套设计对了。