雜記 › 技術筆記 › API 回傳的 JSON 匯出成 CSV,Excel 裡為什麼少了一欄

API 回傳的 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:匯出前需要自己核對每條記錄的鍵集嗎?

頂層不用,巢狀層要過一遍眼睛。頂層鍵由程式碼取聯集、缺的補空字串;要確認的是第二層以下的鍵——勾了展平它們才會變成欄。

📎 相關文章

PNG 壓了三遍只減 426 位元組:無損壓縮的真實邊界 2026-10-22 WebP 轉 PNG 後體積漲了幾倍:什麼時候必須轉,怎麼把漲幅壓住 2026-10-22 截圖縮到四分之一,小字糊成一團的三種原因 2026-10-22 同樣裁 300×300,頭像為什麼總偏右下角半格 2026-10-22 按每月 1.7 倍預估 18 個月,腳本和表格為什麼給出兩個數 2026-10-22