杂记 › 技术笔记 › 接口返回的 JSON 导成 CSV,Excel 里为什么少了一列

接口返回的 JSON 导成 CSV,Excel 里为什么少了一列

三个症状对不上同一条规则

接口返回一段 JSON,丢进表格软件做透视,最先撞见的往往是三件事:某一格显示成 [object Object];印象中该有的一列整列不见了;中文打开是乱码。看着像三个 bug,其实是三条各不相干的规则:键怎么收集、对象怎么写成字符串、编码标记在哪一步拼上去。

样例是三条记录:第一条有 id 与 name,第二条换成 addr 与 tags,第三条只有 name 且值是 null,输入共 92 个字符。默认选项下表头是 id,name,addr,tags,统计写「行: 3 | 列: 4」,全文 4 个 CRLF。

表头是所有键的并集,但它只往上看一层

取字段是把每条记录摊开遍历键再去重,所以表头是所有键的并集、顺序按首次出现。字段只在一部分记录里出现并不会少成一列,它照样进表头,缺的格子补空串——第三条的 name 是 null,归一化成空串,那一行于是是 ,,,。真正少列的是另一回事:关闭展平时嵌套对象只算一层里的一个键,子键进不了表头。

关掉展平,一格值就换成一次 String

不勾展平时,非标量的值直接交给 String():addr 那格来自 String({city:"北京"}) 得 [object Object];tags 那格来自 String(["a","b"]) 得 a,b,方括号与引号都没了,还内嵌分隔符,于是被 auto 整格包起来。勾上展平走另一条分支:对象逐层拆成点号路径,数组才真按 JSON 字符串写,tags 变成 "[""a"",""b""]"。

位置展平关闭展平打开
表头行id,name,addr,tagsid,name,addr.city,tags
数据第 1 行1,"张三,Co.""A""",,同左
数据第 2 行2,,[object Object],"a,b"2,,北京,"[""a"",""b""]"
数据第 3 行,,,,,,

样例里 addr 只有一个子键,两种模式都是 4 列,差别只在列名与取值。子键多起来列数才真不同:[{"a":{"x":1,"y":2}}] 关闭时只有字段 a 一列、值 [object Object],打开时是 a.x、a.y 两列——少的那一列就在这一档丢的。深一层同样:{"a":{"b":{"c":1}}} 关着是 a 配 [object Object],打开是 a.b.c 配 1。顶层是对象而非数组时会被包成一条记录,输出 1 行。

字符 69 字符 UTF-8 73 字节 复制 73 字节 下载 76 字节

图 1|69 个字符按 UTF-8 是 73 字节,带 BOM 下载 76 字节,复制永远是 73 字节。条形按字节等比。

引号是三档,多出的字段是从不加引号造成的

页面把引号写成「是否为每个字段添加引号」,代码里却是 auto、always、never 三档,默认 auto。auto 把引号、当前分隔符与 CR、LF 拼成字符类试值,命中才加引号并把内部引号翻倍;always 无差别加,空字段也写成 "";never 直接返回原值,一个转义都不做。于是第 1 行在 never 档变成 1,张三,Co."A",,,按分隔符切出 5 个字段而表头只有 4 列,预览这一行挤成 5 格,第 2 行同样多 1 个。分隔符只有逗号、分号、Tab 三档,换档只改拼接字符,行尾仍是 CRLF。

字节有三本账,BOM 只记在其中一本上

同一份 CSV 字符数 69(数的是 UTF-16 码元),UTF-8 后 73 字节,勾 BOM 下载 76 字节。差的 4 字节都在那两个字上:张与三各占 3 字节,E5 BC A0 就是张。BOM 是 EF BB BF 这 3 个字节,只在下载那一步拼上去,内容本身恒不含:下载 76 字节、复制 73 字节,即使那个复选框默认是勾着的。

行尾用 CRLF,一共 4 个:表头 1 个加 3 条数据行各 1 个。结尾也是 CRLF,切分后最后一段是空串,被丢掉后预览 4 行。Windows 按 CRLF 认行,RFC 4180 也要求 CRLF;Unix 按 LF 数到的同样是 4,但每段尾部残留一个 CR 会跟着进最后一个字段。

放到 1000 行,同一格的影响乘一千遍

放大成 1000 条乘 6 字段,默认选项、关闭展平:统计 行: 1000 | 列: 6,字符 39316、UTF-8 43316 字节、加 BOM 43319,CRLF 1001 个。addr 那一列每行都是 [object Object],共 1000 次,整列从头到尾没内容。打开展平后字段变 id,name,age,addr.city,tags,active,仍 6 列,字符降到 35321、字节反升到 43321:那一格从纯 ASCII 换成中文城市名,两本账就不再同向。想先看清哪几层有子键,JSON 树形查看比先看 CSV 直接。

关·字符 39316 关·字节 43316 关·加BOM 43319 开·字符 35321 而字节 43321

图 2|关闭展平 39316 字符对 43316 字节,加 BOM 到 43319;打开展平字符少而字节多,[object Object] 有 1000 格。

文件名与这些无关:data_1700000000000.csv 是固定时刻后的写法,共 22 个字符——前缀 5、13 位毫秒时间戳、扩展名 4。这些规则我都在 JSON 转 CSV 上按同一份输入逐条核对过,勾与不勾的差别就是那两列取值。

报错的那句话来自引擎,不来自工具

  • 顶层是标量时抛固定的一句 数据不是数组或对象,123、true、null 与 "abc" 四种输入的消息逐字相同。
  • 语法错误显示引擎原文:输入 {a:,Node v24 与 Chrome 154 实测完全相同,为 Expected property name or '}' in JSON at position 1 (line 1 column 2),界面原样写进去,换浏览器就换措辞。
  • 输入清空后统计归成 行: 0 | 列: 0,此时点下载弹「请先输入有效的JSON数据」。

Q:为什么某一行的字段比列多?

因为引号选了从不加引号。值里只要有当前分隔符,切回去就多出一段:样例那行切出 5 个字段而表头是 4 列。改回 auto 或 always 就重新对齐。

Q:BOM 到底要不要勾上?

得分出口。勾上只影响下载:文件开头多 EF BB BF 这 3 个字节,本例从 73 变 76;复制出来永远是 73 那份。所以复制粘贴进已开着的窗口时别指望这个勾选,落成文件再双击就保留默认值。JSON 导入 Excel 那篇单独讲过 CSV 中文乱码这些坑。

Q:数组里套对象会被怎么处理?

两种口径都不产生多行。关闭展平时数组走 String,得去掉方括号与引号的 a,b;打开时走 JSON 字符串化得 ["a","b"],再由 auto 把内部引号翻倍。对象在关闭时是 [object Object],打开时被拆成点号路径。

Q:导出前需要自己核对每条记录的键集吗?

顶层不用,嵌套层要过一遍眼睛。顶层键由代码取并集、缺的补空格;要确认的是第二层以下的键——勾了展平它们才会变成列。

📎 相关文章

把质量调到 100 再压一次,图片其实一个字节都没动 2026-10-21 同一个数打两遍,最小公倍数成了 94906266.99999999:先乘后除撞上限的是中间量 2026-10-21 后缀从最后一个点算起:LICENSE、.gitignore、archive.tar.gz 为什么会漏改 2026-10-21 批量建目录的脚本里,编号补零和 CON 这两个坑最容易被忽略 2026-10-21 同一句中文,UTF-8 存 12 字节、GBK 只存 8 字节:编码是怎么被判错的 2026-10-21