news 2026/11/2 3:25:04

WPS表格转JSON实战:JS宏、加载项与数据清洗

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WPS表格转JSON实战:JS宏、加载项与数据清洗

WPS表格里那点数据要变成JSON,很多人第一反应是"另存为"里翻一遍,找不到,然后开始怀疑是不是版本不对。我最初做接口联调的时候也干过这事——把表格扔进某个在线转换网站,粘出来发现中文全是问号,长订单号末尾变成了0,日期变成一串四五万的数字,改一轮比重录一遍还慢。后来索性把这条链路自己搭了一遍,从宏到加载项都试过,现在基本是"表格改完点一下按钮,JSON直接落到指定目录"的状态。这篇就把我趟过的路完整写出来:WPS原生为什么不给JSON、三条可行路径各自适合什么场景、数据源要先做哪些清洗、宏和加载项的代码怎么写、一对多的嵌套结构怎么拼、导出的结果怎么验。不管你是刚接触表格转JSON的新手,还是已经在写脚本但总在细节上翻车的老手,应该都能从里面挑到能直接抄的部分。

1. 先把路径选对:WPS导出JSON到底有几种走法

1.1 原生"另存为"的格式清单里为什么没有JSON

打开WPS表格的另存为对话框,格式下拉里能看到的无非是自家的.et、微软的.xlsx和.xls、.csv、.txt、.pdf、.html、.dbf这些。这个清单不是随便定的,它反映的是"这一层该由谁负责"的分工:电子表格软件负责的是单元格网格的存取,JSON是描述对象结构的数据交换格式,两者中间隔着一层"结构映射"——哪一行是字段名、哪一列是数组、哪些行该合并成一个对象,这些规则表格软件没法替你猜。所以官方不给这个入口,不是遗漏,而是这件事本身就没有唯一正确答案。

理解这一点很关键,因为它决定了你后面所有方案的形态:JSON导出本质上是一个"你定义规则、程序执行规则"的过程,规则写得越明确,导出的结果越稳。我见过太多人把希望寄托在某个"一键转换"按钮上,结果每次表格结构微调一下,导出结果就面目全非,就是因为规则藏在工具里而不是握在自己手里。

顺带说一句,CSV确实是个中间站——先把表格存成CSV,再用一段脚本读CSV生成JSON。这条路人最少、门槛最低,但坑也集中:CSV默认编码在不同环境下可能是GBK,中文会烂;字段里本身带逗号或换行时,必须靠双引号包裹,解析器稍不注意就错位;空值和空字符串在CSV里长得一模一样。所以CSV方案我只在"一次性、字段简单、数据量不大"的场合推荐。

1.2 三条路径的横向对比:宏、加载项、外部脚本

我把实际用过的三条路整理成一张表,你可以对着自己的场景挑:

路径实现方式上手成本维护成本适合场景主要短板
宏(VBA)表格内嵌VBScript风格代码,手动运行或绑按钮中,需要装VBA运行环境中,代码跟着文件走单人、单文件、偶发导出环境不一定有,跨机器容易失效
宏(JS宏/JSA)表格内置JavaScript宏编辑器,语法是现代JS低,无需额外安装低日常高频、需要处理复杂逻辑文件读写接口在不同版本上命名有差异
加载项用HTML/JS写功能区按钮,通过JS API操作文档高,要打包注册低,一次做好全员可用团队共用、固定流程调试链路长,注册环节容易卡住
外部脚本Python/Node读xlsx或csv再输出中,要有运行环境中,表格结构变了要改脚本批量、定时、接入流水线脱离表格界面,非技术同事用不了

我的实际选择是:日常自己用走JS宏,因为WPS现在内置了宏编辑器,不用额外装东西,语法又是JS,JSON.stringify直接能用,省掉手写转义的一大堆麻烦;要给整个小组用,就做成加载项,在功能区挂一个按钮,谁点都一样;如果是每天定时从一堆表格里抽数据进系统,那就放到外部脚本里跑,跟表格彻底解耦。

这里有个容易被忽略的判断依据:导出动作是谁触发的。如果是"人看着表格、觉得改完了、顺手导一下",那答案一定在表格内部(宏或加载项);如果是"程序定时跑、没人盯着",那就别硬塞进表格里,外部脚本更省心。反过来,把定时任务做成宏,得靠系统计划任务去唤起表格进程,稳定性差得不是一点半点。

还有一点,VBA在WPS里不是默认就有的,通常要单独装一个VBA宏运行环境组件;而JS宏是内置的。所以如果你的机器是干净环境、又不想折腾安装,直接奔JS宏去,能省掉一半的沟通成本——尤其是帮同事配环境的时候,你让他装个组件,他可能装完发现版本对不上,又回来找你。

2. 数据源先过关:表格结构决定了JSON能不能用

2.1 表头那一行就是schema,别随手写

JSON对象的键名全部来自表头,所以表头不是"给人看的标题",而是实打实的字段定义。我在项目里见过表头写成"客户名称(必填)"、"金额(元)"、"备注 "(末尾带空格)的,导出来键名里带括号带空格,前端拿到直接懵。规范的做法是:表头只用字母、数字、下划线,首字符不用数字,需要说明的信息放到单元格批注或者第二行说明里去,不混进键名。

命名风格也要统一。要么全小写下划线(order_no),要么全驼峰(orderNo),一个表里混着来,后面写消费端代码的人会很难受。我一般的习惯是跟后端接口字段对齐,表格直接照抄接口文档的字段名,这样导出的JSON几乎不用改就能塞进请求体。

另外,表头行的位置要固定。有些人喜欢在上面加个标题行、加个导出时间、加个制表人,结果第一行不是字段名。宏里如果按"第一行是表头"写死,就会把标题行当成字段。稳妥的写法是在代码里配置一个headerRow变量,明确告诉程序表头在第几行,别指望自动识别。这个参数虽小,但能挡掉后面一大类莫名其妙的错误。

还有一个细节:空表头列必须跳过。表格右边经常有临时计算的辅助列,表头是空的。如果代码不判空,就会生成一个键名为空字符串的字段,虽然JSON语法上合法,但消费端解析时会出问题。判断逻辑很简单,取到键名后trim一下,为空就continue。

2.2 合并单元格、隐藏行和空行:三个沉默的数据杀手

合并单元格是表格转JSON的头号敌人。Excel和WPS在存储合并区域时,只有左上角那个单元格真正持有值,其余单元格是空的。你眼睛看到的是"华东区"占了五行,程序读到的是一行有值、四行空。所以导出前必须做一次"向下填充":把上一个非空值填到下面的空单元格里。这个动作可以在临时副本上做,不破坏原表,我在代码里通常是复制一份工作表再填充,导完就删。

隐藏行的问题更隐蔽。用户筛选之后隐藏了一批行,或者手动隐藏了几行草稿数据,视觉上看不见,但UsedRange是包含隐藏行的,导出结果里就混进了不该出现的数据。如果业务上确实需要"只导可见行",就要在循环里加一个RowHeight > 0或者Hidden = False的判断;如果本意是全量导出,那就在操作说明里写清楚"导出前请取消筛选",避免两边理解不一致。

空行的处理要看约定。全部是空值的行,一般应该跳过,否则会生成一堆{"a":"","b":""}的垃圾对象。但有些业务里"空行"是有意义的占位,那就得保留。我的默认做法是跳过全空行,同时在日志里打出跳过的行号,方便回头核对是不是误跳了。

提示:导出前先另存一份副本再操作,尤其是要做填充、删行这类破坏性动作的时候。我吃过一次亏,填充操作直接写在了原表上,同事后来拿着那张表做汇报,发现数据"重复"了,解释了半天。

还有一个容易忘的点:表格末尾的空白行。有些人习惯性在数据下面敲几个回车,UsedRange的边界就被撑大了。判定数据末行时不要用UsedRange.Rows.Count,而应该从下往上找第一个非空行,这个逻辑在任何语言里都是几行代码的事,但能省掉大量"为什么多出来三个空对象"的追问。

2.3 数字、日期、前导零:类型丢失的三个高发区

先说什么叫类型丢失。JSON是有类型的:数字123和字符串"123"是两回事。表格单元格没有这么严格的类型概念,它是靠格式显示出来的。程序读单元格时,读到的可能是显示值,也可能是原始值,两种模式结果完全不一样。

第一类高发区是长数字。订单号、身份证号、银行卡号这种超过15位的数字,在表格里一旦被识别成数值,末尾就会被舍入成0,而且这个丢失是发生在录入那一刻的,导出环节再努力也救不回来。唯一的解法是录入时就设成文本格式,或者在数字前面加个单引号。导出代码里可以再加一道保险:对这类字段单独做字符串化处理,确保输出的是带引号的字符串。

第二类是高发区日期。表格里的日期本质是一个序列号,2024年1月1日存的是45292。如果代码直接取值,导出结果就是45292,消费端拿到一脸懵。处理办法是判断单元格格式是不是日期类型,是的话按yyyy-mm-dd格式化成字符串再输出。有些场景需要带时分秒,那就用yyyy-mm-dd hh:mm:ss。统一格式这件事一定要在导出层做死,别指望下游去猜。

第三类是前导零。像007、021这种编码,被当数字处理后前导零没了。同样只能靠文本格式解决。我在代码里会维护一个"强制文本字段"清单,配置成数组,命中就按字符串输出,其余按类型自动判断。这个清单是整个导出逻辑里最值得维护的东西,字段加一个改一行,比到处改判断条件清爽得多。

数值精度也要留意。小数运算在浮点数里天然有误差,19.9 * 3在某些语言里会得到59.699999999999996。导出前对金额类字段做一次保留两位小数的处理,输出就干净了。这个处理放在导出层做,别改原表,原表还得给人看。

3. 用JS宏做一次"另存为JSON":最小可用实现

3.1 宏编辑器在哪、怎么进入

WPS表格的功能区里找"开发工具"选项卡,里面能看到"JS宏"或者"宏编辑器"的入口,点开就是内置的代码编辑器。跟VBA不一样,这个不需要额外装运行环境,装好WPS就能用。第一次打开可能需要在设置里把宏安全性调一下,否则保存的宏会被拦。这个设置项的位置各版本略有差异,一般在"文件 - 选项 - 信任中心"之类的分类下。

宏文件跟着工作簿走,存成支持宏的格式(带m的xlsm,或者WPS自家的格式),下次打开还在。如果你希望这段逻辑在所有表格里都能用,可以把它放到个人宏工作簿里,那样每次启动都会加载。我个人是习惯每个业务表单独存一份,因为不同表的字段规则本来就不一样,放全局反而容易搞混。

调试的时候有个小技巧:在代码里用Console.log()输出中间结果,编辑器底部一般会有输出窗口。别一上来就对着最终JSON调,先把"读到的表头数组"和"第一行的值数组"打出来看看,八成的问题在这一步就能发现——要么表头读错位了,要么数据区起始行算错了。

3.2 读取区域与遍历的核心代码

下面这段是我常用的骨架,用的是WPS内置的JS宏语法,思路跟Excel的JS API基本一致:

function sheetToJson() { const app = Application; const wb = app.ActiveWorkbook; const sh = wb.ActiveSheet; // 表头行号,按实际情况改 const headerRow = 1; const startRow = headerRow + 1; const maxRow = sh.UsedRange.Row + sh.UsedRange.Rows.Count - 1; const maxCol = sh.UsedRange.Column + sh.UsedRange.Columns.Count - 1; // 收集表头 const headers = []; for (let c = 1; c <= maxCol; c++) { const v = sh.Cells(headerRow, c).Text; const key = v == null ? "" : String(v).trim(); headers.push(key); // 空键名后面跳过 } const rows = []; for (let r = startRow; r <= maxRow; r++) { const obj = {}; let isEmpty = true; for (let c = 1; c <= maxCol; c++) { const key = headers[c - 1]; if (!key) continue; const cell = sh.Cells(r, c); const val = readCell(cell); if (val !== "" && val !== null) isEmpty = false; obj[key] = val; } if (!isEmpty) rows.push(obj); } const json = JSON.stringify(rows, null, 2); Console.log(json); return json; }

几个位置值得说明。UsedRange给出的行列数包含了格式残留,所以先用它划个大范围,再用"全空行跳过"的方式把噪声滤掉,比精确计算边界更省事。sh.Cells(r, c).Text取的是显示文本,对于日期和格式化过的数字,它给的是你眼睛看到的样子,这恰恰是我们想要的;如果取.Value2,拿到的是原始值,日期就会变成序列号。这个区别非常关键,我在早期版本里用了Value2,结果所有日期字段导出来都是四万多的数字,排查了半天才发现是取值方式的问题。

不过.Text也有副作用:列宽不够的时候,显示可能是###。所以对数值列,我建议保留一次原始值兜底——如果文本是###,就退回用.Value2。这个判断写起来也就三行。

3.3 类型判断函数与字段白名单

readCell是我封装的一个小函数,负责把单元格的值转成JSON里合适的类型:

const TEXT_FIELDS = ["orderNo", "idCard", "phone", "zipCode"]; function readCell(cell) { const raw = cell.Text; if (raw === null || raw === undefined || raw === "") return ""; // 列头命中强制文本清单,直接返回字符串 const colKey = String(cell.EntireColumn.Cells(1, 1).Text || "").trim(); if (TEXT_FIELDS.indexOf(colKey) >= 0) return String(raw).trim(); const s = String(raw).trim(); // 日期格式:按 yyyy-mm-dd 输出 if (/^\d{4}[-\/]\d{1,2}[-\/]\d{1,2}$/.test(s)) { const d = s.replace(/\//g, "-").split("-"); return d[0] + "-" + pad2(d[1]) + "-" + pad2(d[2]); } // 纯数字且不超过15位,转成数值 if (/^-?\d+(\.\d+)?$/.test(s) && s.replace(/[-.]/g, "").length <= 15) { return Number(s); } return s; } function pad2(x) { return String(x).padStart(2, "0"); }

白名单那段是这里的重点。很多人写类型判断只按"能不能转成数字"来定,结果007变成7,13800138000变成数字后在某些语言的序列化里又被转成科学计数法。加一个强制文本清单,把这几类字段钉死成字符串,问题就没了。这个清单不需要很长,通常就五六项,但省下来的排查时间非常可观。

日期判断用正则而不是靠单元格格式属性,是因为格式属性的枚举值在不同版本、不同区域设置下不完全一样,用显示文本的模式匹配更稳。代价是必须约束录入格式,所以我会在表格上加数据验证,让日期列只能按yyyy-mm-dd录。约束入口永远比事后补救划算。

3.4 把结果落盘:编码这个坑必须提前躲

最省事的落盘方式是先输出到剪贴板或者新建一个工作表,让用户自己复制。但真到批量场景,还是要写文件。JS宏里写文件的接口在不同版本上命名有差异,稳妥的做法是用系统对话框拿到保存路径,再用当前版本提供的文件写入能力落盘;如果版本不支持直接写,就退一步写到临时工作表里再手动另存。

如果你走的是VBA路线,这里有个必须知道的坑:用FileSystemObject.CreateTextFile写文本时,第三个参数是"是否Unicode",传True写出来的是UTF-16LE,浏览器打开就是乱码;传False写的是ANSI,中文同样烂。正确的写法是用ADODB.Stream,显式指定UTF-8:

Dim st As Object Set st = CreateObject("ADODB.Stream") st.Type = 2 ' 文本流 st.Charset = "utf-8" st.Open st.WriteText jsonStr st.SaveToFile savePath, 2 ' 2 = 覆盖写入 st.Close

这个细节我在别的文章里也提过,因为真的太容易踩。判断依据很简单:用系统自带的记事本打开文件,如果中文正常,编码基本没问题;如果显示成方块或者问号,那就是编码没对上。

4. 做成加载项:把转换固化成一个常驻按钮

4.1 加载项的基本骨架长什么样

宏的问题是每次都得打开编辑器点运行,对非技术同事不友好。加载项把这段逻辑包成一个功能区按钮,点一下就出结果,体验上跟原生功能没区别。代价是搭起来麻烦一些,要有几个固定文件。

一个最小的加载项大概包含这么几块:一个描述元信息的清单文件,声明名称、版本、入口页面;一个HTML页面作为容器;一个JS文件写业务逻辑;还有一个描述功能区的配置文件,定义按钮叫什么、图标用什么、点了调哪个函数。把这些打包成一个目录,通过WPS的加载项管理入口注册进去,重启后功能区就能看到按钮。

注册这一步是新手最容易卡住的地方。常见现象是文件都放对了,功能区就是不出按钮。我的排查顺序是:先确认注册的路径指向的是包含清单文件的那一层目录,而不是它的上一级;再确认清单文件里的入口文件名跟实际文件完全一致,大小写也要对上;最后确认是不是被安全设置拦了,通常会有提示,但有时候提示被折叠起来看不见。

4.2 调用表格对象的关键 API

加载项里拿文档对象的方式跟宏不太一样,表格类应用一般是通过专门的入口函数取到应用对象,再往下拿工作簿和工作表:

function exportJson() { const app = wps.EtApplication(); // 表格应用对象 const wb = app.ActiveWorkbook; const sh = wb.ActiveSheet; const used = sh.UsedRange; const rowCount = used.Rows.Count; const colCount = used.Columns.Count; const out = []; for (let r = 2; r <= rowCount; r++) { const item = {}; for (let c = 1; c <= colCount; c++) { const key = String(sh.Cells(1, c).Text || "").trim(); if (!key) continue; item[key] = sh.Cells(r, c).Text; } out.push(item); } const text = JSON.stringify(out, null, 2); saveToFile(text); }

这段逻辑跟宏里几乎一样,差别只在取对象的入口。所以我的建议是:先用宏把逻辑调通,再整体搬到加载项里。宏的调试反馈快,改一行立刻能跑;加载项每次改完要重新加载,效率低得多。等逻辑稳定了再搬,能省不少时间。

saveToFile这一步在加载项里反而更好办,因为运行在Web环境里,可以直接用浏览器提供的下载能力,或者调用宿主提供的文件对话框。这就绕开了宏里那套编码折腾——Web环境的字符串本身就是UTF-8,写出去不会乱码。这也是我推荐团队场景走加载项的原因之一。

4.3 让按钮更好用的几个小设计

第一个设计是记住上次的配置。把表头行号、强制文本字段清单、输出目录这些参数存在本地,下次打开自动带出来。用户不用每次重新填,出错概率也低。存储用浏览器本地存储就行,简单可靠。

第二个设计是导出前做一次体检。点按钮之后先扫一遍数据,把"表头有空列""存在合并单元格""某列全部为空""前五行有重复键名"这些问题列成一个清单弹出来,让用户确认再导。这个功能看起来是锦上添花,实际用下来能挡掉至少一半的返工——很多人根本不知道自己表里有合并单元格,直到导出的JSON少了四分之三的数据。

第三个设计是导出后给个回执。不要只弹一句"导出成功",要给出具体信息:导出了多少条记录、输出到了哪个路径、耗时多少。我还会在回执里放一个"打开所在文件夹"的按钮。这些细节看着小,但对天天用的人来说,省下的就是实打实的时间。

5. 一对多结构:平表怎么拼成嵌套JSON

5.1 先想清楚"一行代表什么"

前面所有代码都基于一个假设:一行就是一个JSON对象。但真实业务里经常不是这样。最典型的是订单明细:订单号、客户名在一行里重复出现,每行对应一个商品。直接平铺导出,得到的是一个扁平数组,消费端还得自己按订单号分组。这时候就得在导出环节做聚合。

动手之前必须先回答一个问题:这一行代表的最小实体是什么。如果一行代表"一条明细",那JSON的最外层就应该是明细数组;如果一行代表"一个订单",那就要按订单号把多行合并成一个对象,明细挂在子数组里。这个问题想不清楚,代码怎么写都是错的。我在评审别人的导出脚本时,第一句问的永远是这句。

这里还有一个陷阱:分组键必须唯一且稳定。用订单号分组是安全的,用"客户名+日期"就危险,因为同一天同一客户下两单的情况太常见了。如果表里没有天然的唯一键,就在表格里加一列自增序号,别硬凑。

5.2 分组聚合的实现思路

核心就是一个哈希表,边遍历边往里塞:

const map = new Map(); for (let r = startRow; r <= maxRow; r++) { const orderNo = String(sh.Cells(r, orderCol).Text || "").trim(); if (!orderNo) continue; if (!map.has(orderNo)) { map.set(orderNo, { orderNo: orderNo, customer: String(sh.Cells(r, custCol).Text || "").trim(), items: [] }); } map.get(orderNo).items.push({ sku: String(sh.Cells(r, skuCol).Text || "").trim(), qty: Number(sh.Cells(r, qtyCol).Text || 0), price: Number(sh.Cells(r, priceCol).Text || 0) }); } const result = Array.from(map.values());

这段代码有两个地方值得说。一是"主表字段取第一次出现的值",也就是订单号和客户名只在第一次遇到这个键时写入,后续行不再覆盖。如果后面某一行的客户名跟第一行不一致(表格填错了),当前逻辑是静默取第一个,不会报错。我的做法是加一个校验:如果发现不一致,把订单号记到异常清单里,导完提示用户。数据质量问题不该被代码掩盖掉。

二是明细的顺序。Map在现代JS里保持插入顺序,所以明细的先后跟表格里的行序一致。如果业务上要求按SKU排序,就在最后加一次sort,别指望表格里本来就是排好的。

更深一层的嵌套也是同样的道理,一层层套哈希表就行。但我要提醒一句:嵌套超过三层,就该考虑是不是表格设计有问题了。三层以上的结构,用表格维护本身就很痛苦,强行导出的结果往往连制表人自己都说不清。这种情况我更倾向于拆成多张表,用外键关联,导出时再拼。

5.3 导出之后的校验动作

JSON这东西有个特点:语法错了会立刻报错,但结构错了(比如字段名拼错、少了一层数组)语法上完全合法,肉眼也不容易发现。所以导出后必须验。

最低成本的验法是回读一次:把导出的文本用JSON.parse解析一遍,能过说明语法没问题;再抽查第一条和最后一条记录,对着表格看一眼字段值对不对。这一步我基本是条件反射,导出后必做。

再稳一点的做法是写一个校验函数,检查三件事:记录条数是否等于表格里的有效行数(分组场景则是唯一键的个数)、每条记录的键集合是否完全一致、必填字段有没有空值。这三条过一遍,九成的问题都能拦住。

如果是要对接后端接口,最好在导出后直接发一次测试请求。我之前吃过一次亏,本地JSON看着完全正常,发到接口才发现后端要的是{"data": [...]}包一层,而我们导的是裸数组,白折腾一轮。接口文档里的示例报文,值得在导出代码里照着抄一遍结构。

6. 实测中的偏差排查:这些现象对应这些原因

6.1 常见问题对照表

下面这张表是我这些年攒下来的,基本覆盖了九成以上的"导出来不对":

现象大概率原因处理办法
中文变成问号或方块写入文件时用了ANSI或UTF-16编码显式指定UTF-8,用记事本验证
长订单号末尾变0单元格在录入时已被识别为数值,精度丢失录入即设文本格式,导出时按字符串处理
日期变成四五万数字取的是原始值而不是显示文本改用显示文本取值,或按日期格式转换
数字字段带上了引号统一按字符串输出,没做类型判断加数值正则判断,命中转Number
记录数比表格行数少存在全空行被跳过,或分组时按唯一键合并核对有效行数与唯一键个数
记录数比表格行数多UsedRange包含了格式残留的空行从下往上找第一个非空行作为边界
合并单元格只导出了第一行只有左上角单元格持有值导出前做向下填充
JSON解析报错拼接时多逗号、少括号,或字符串未转义用JSON.stringify,别手拼字符串
筛选后的数据也被导出了循环没判断行是否隐藏加可见性判断,或在说明里写明需取消筛选
点按钮没反应加载项注册路径不对,或入口函数名不匹配逐项核对路径、文件名、大小写

这张表建议存下来。遇到问题先对一遍,比从零开始查快得多。我发现很多人排查时喜欢直接改代码试,改了三版反而把原本对的地方也改坏了。先定位现象、再对原因,最后才动代码,这个顺序不能乱。

6.2 一次完整的排查链路复盘

说个真实的排查过程。某次导出的JSON,接口那边反馈说"客户名全是undefined"。我先做的是最小复现:把表格砍到只剩三行两列,导出,问题依旧,说明跟数据量无关。接着把导出的文本打印出来,发现键名是"客户名称",值是有的,但接口读的是customerName。到这一步就清楚了——不是导出坏了,是字段名没跟接口对齐。

但我没有立刻去改代码,而是先确认了一件事:这张表的字段名是谁定的。问下来是业务同事按自己的习惯写的表头,压根不知道有接口字段这回事。所以真正的修复不是改代码里的一行映射,而是在表格里加一行"字段名对照",让表头保持业务可读,导出时按对照表翻译。这个改动看起来更麻烦,但它解决的是根因,下次换个人填表也不会再错。

这件事给我留下一个习惯:遇到"值不对"先分清是取错了值还是名字对不上。取错值是技术问题,去查代码;名字对不上是协作问题,去查约定。两者的修法完全不同,混在一起查只会越查越乱。

还有一类问题出在"看起来对但就是不对"。比如导出的数字1.0变成了1,JSON里没法区分整数和浮点,这是格式本身的特性,不是bug。再比如对象里字段的顺序跟表格列顺序不一致,很多语言的JSON实现会重排键,消费端如果依赖顺序就会出问题。这类情况要在文档里写清楚,别指望靠代码去"修"——那是跟标准较劲。

最后说一个操作层面的建议:把每次导出都留一份带时间戳的副本。JSON文件很小,占不了多少空间,但出了问题的时候能立刻对比"上一版是什么样"。我现在的做法是文件名后面直接缀上20240513_1420这样的时间戳,谁改的、什么时候改的,一目了然。这个小习惯救过我至少两次,尤其是在多个人轮流维护同一张表的时候。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 3:23:59

实测RTX 4060 Ti超频:核心+37%推理仅快6%,内存墙才是瓶颈

先说结论&#xff1a;我把手头那张16GB显存的华硕RTX 4060 Ti核心频率从锁定的2000MHz一路拉到2745MHz&#xff0c;频率涨了37%&#xff0c;但跑Qwen2.5-7B-Instruct的AWQ 4-bit模型&#xff0c;生成吞吐只从51.8 tokens/s涨到55.1 tokens/s&#xff0c;换算下来提速6%出头。反…

作者头像 李华
网站建设 2026/11/2 3:23:10

线程同步与互斥:原理、优化与实战案例

1. 线程同步与互斥的本质需求当多个线程同时访问共享资源时&#xff0c;就像十字路口的车流交汇&#xff0c;如果没有交通信号灯的协调&#xff0c;必然导致混乱和事故。我在处理电商秒杀系统时&#xff0c;曾遇到过库存数据错乱的严重问题&#xff1a;明明只卖出了100件商品&a…

作者头像 李华
网站建设 2026/11/2 3:22:40

Aspen Plus建模氯化锂吸收式制冷系统实战指南

1. 项目概述&#xff1a;为什么氯化锂吸收式制冷值得用Aspen深挖最近在某高校实验室带学生做热力系统仿真时&#xff0c;好几个课题组不约而同把目光投向了氯化锂吸收式制冷系统——不是因为它是新概念&#xff0c;恰恰相反&#xff0c;它是个有四十多年工程积淀的老技术&#…

作者头像 李华
网站建设 2026/11/2 3:21:39

FastAPI高并发优化:从架构到代码的实战指南

1. 项目概述&#xff1a;并发性能优化的核心挑战当面试官抛出"如何提升项目并发性能"这个问题时&#xff0c;实际上是在考察候选人对现代高并发系统设计的全链路理解能力。我在金融级分布式系统和AI服务部署中积累的实战经验表明&#xff0c;真正的性能优化需要从协议…

作者头像 李华
网站建设 2026/11/2 3:20:42

专科生论文写作:10款AI工具实测与组合方案

1. 项目概述作为一名在学术写作领域摸爬滚打多年的老手&#xff0c;我深知论文写作对专科生的挑战。从选题到文献综述&#xff0c;从格式排版到查重降重&#xff0c;每个环节都可能成为拦路虎。最近半年&#xff0c;我系统测试了市面上主流的10款AI论文辅助工具&#xff0c;发现…

作者头像 李华
网站建设 2026/11/2 3:19:07

3D圆球抽奖系统:企业年会的炫酷解决方案

1. 项目背景与核心功能年会抽奖环节一直是企业活动中最受期待的环节之一。传统的抽奖方式往往缺乏视觉冲击力&#xff0c;难以调动现场气氛。这个基于Windows平台的3D圆球抽奖系统&#xff0c;正是为了解决这个问题而设计的。核心功能亮点&#xff1a;炫酷的3D球体动态效果&…

作者头像 李华