使用 Python、Node.js 或 jq 的命令列管線能在毫秒內將 CSV 轉換為 JSON,但只要真實世界的 CSV 一旦偏離教科書般的範例,它就例行性地會把加引號的逗號、內嵌的換行字元,以及原本的字串型別弄得一團亂。當你已經有現成的腳本、可重現的管線,而且 CSV 的結構從不令人意外時,命令列就能發揮所長。當資料較為敏感、資料列內含逸出引號或換行,或你只是想貼上、轉換、複製而不想安裝任何相依套件時,瀏覽器型的轉換工具就能發揮所長。兩種做法在完美的檔案上會產出相同的 JSON,但在損壞的檔案上則會以不同的方式失敗。差異出現在三個地方:型別推斷、引號欄位的處理,以及在轉換過程中位元組實際傳輸的位置。本指南會逐一說明各個做法在哪裡會出錯,接著展示本機、符合 RFC 4180 風格的轉換器如何讓每個儲存格都保持 JSON 字串,而且不讓任何位元組離開你的機器。

csv to json command line vs online
CSV 轉 JSON:命令列 vs 線上工具的實務比較

各個做法的實際運作方式

命令列的做法把 CSV 視為純文字,將其路由經過一個或多個可執行檔,這些程式會讀取記錄、建立資料結構,然後輸出 JSON。常見的組合包括:使用 csv 與 json 模組的 Python 單行指令、呼叫 csv-parse 與 JSON.stringify 的 Node.js 腳本,或是先用 awk 或 sed 做輕度前處理、再餵給 jq 的 shell 管線。瀏覽器做法則將同一個檔案當作文字放進文字區域中,用 JavaScript 狀態機進行解析,然後將 JSON 寫入可下載的 Blob 或寫入系統剪貼簿。兩種做法預設都不會上傳你的檔案,但託管的線上轉換器通常會上傳,除非該頁面明確標示為本機處理。因此,兩者之間的差異通常與速度無關,幾乎總是出在處理方式與資料所在位置上。

命令列工具與其真實的權衡取捨

Python 加上標準程式庫是最常見的起點,因為不需要額外安裝。csv.DictReader 類別會將第一列當作標頭,並為每個資料列產生一個字典,json.dumps 可以直接將其序列化。這種組合在簡單的 CSV 上運作良好,但會繼承 Python 對世界的看法:空字串仍是字串,但任何可以被解析為數字或布林值的內容,在你將結果載入另一個語言的那一刻,就會悄悄改變型別。

jq 是為 JSON 而設計的,不是為 CSV 設計的,因此典型做法是呼叫 csv2json 這類輔助工具,或是先用 awk 步驟讓每列輸出一個 JSON 物件,再用 jq 包起來。這種做法適用於沒有引號欄位、只有逗號的扁平資料。一旦某個欄位內含位於引號內的字面逗號,awk 腳本就會開始輸出格式錯誤的結果,而下游的 jq 呼叫就會在第一筆違規記錄上失敗。

Node.js 搭配 csv-parse 提供貨真價實的 RFC 4180 風格解析器,包含引號欄位、逸出引號與混合換行符號。它是最接近嚴格瀏覽器工具的命令列選項,但仍需要安裝 Node、安裝套件,以及一份你必須自行維護的腳本。對於在單一機器上的一次性轉換而言,維護成本往往超過所帶來的好處。

更深層的權衡在於型別推斷。Python 的 csv 模組交給你的是字串,JSON 接著會保留這些字串。一旦任何包裝腳本嘗試用 int() 或 bool() 來清理資料,這份契約就會在不知不覺中被改變。字串 001 會變成數字 1,字面 true 會變成布林值 true,而字面 null 會變成 JSON null。原本預期收到原始字串的接收端 API,現在會拒收這些資料,或存入錯誤的值。

瀏覽器型工具表現優於命令列的時機

當腳本開始需要的程式碼比資料本身還多時,瀏覽器型轉換器就找到了它存在的價值。以下是幾個常見的觸發條件:

  • 檔案所在機器沒有安裝 Python、Node 或 jq,而安裝其中一個所帶來的麻煩,比這次轉換本身還要大。
  • 資料內含引號內的逗號、雙引號或欄位內的換行,而手邊的 awk 或 sed 管線並不認得引號分隔符號。
  • 欄位標頭包含空格、變音符號或標點符號,下游的 JSON 消費者處理起來容易出問題,而你希望在匯入前先做一次健全性檢查。
  • 這是一次性的轉換,而非夜間管線的一部分,這時一個 60 秒就能搞定的瀏覽器工具會勝過 30 行的腳本。
  • 資料敏感度高,即便透過 HTTPS 上傳也是完全不可行的選項。

一個完全在瀏覽器分頁中執行的本機轉換器,能同時繞過「安裝」與「上傳」這兩個問題。CSV 轉 JSON 轉換器使用一個四狀態機來解析貼上的文字,該狀態機對應受限的 RFC 4180 方言,接受 CRLF、LF 與 CR 作為記錄結尾,並且絕不會把值強制轉換為其他型別。最後一個特性,正是大多數命令列腳本會悄悄違背的契約。

使用本機瀏覽器工具將 CSV 轉為 JSON

  1. 貼上以逗號分隔、第一筆記錄含有非空白且不重複之標頭名稱的 CSV。標頭順序決定輸出陣列中的屬性順序,每個標頭都必須精確且區分大小寫,任何重複或空白的標頭都會在產生任何輸出之前被拒絕。一個前導的 U+FEFF 會被消耗掉,因此常見的 UTF-8 BOM 檔案即使在第一個標頭被加引號時也能正常運作。
  2. 轉換輸入並檢查摘要資訊。摘要會顯示資料列數、欄位數、總資料儲存格數,以及編碼後的 UTF-8 輸出大小,讓你能在匯出前先確認輸入已被正確讀取。如果觸及任何上限,轉換會明確失敗,而不是產出寫到一半的檔案。
  3. 檢查每個值是否都仍然是 JSON 字串。抽查一段含前置零的代碼、一段字面 null,以及一個含逗號的引號欄位,驗證 001 仍然是 "001"、null 仍然是 "null",而內嵌的逗號仍留在欄位內,不會把欄位切成兩半。轉換器絕不會根據拼法去猜測數字、布林值、null、日期、公式或空值。
  4. 將 JSON 複製到剪貼簿,或下載 converted.json。下載檔案是透過 Blob 搭配一個新核發的 ObjectURL 產生;編輯 CSV 會撤銷先前的 URL 並清空輸出,這樣瀏覽器中不會殘留任何失效的連結。剪貼簿請求會帶上一個世代識別資訊,因此較舊的授權提示無法在新轉換之後恢復其狀態。

並排比較:命令列 vs 本機瀏覽器工具

考量面向命令列管線本機瀏覽器工具
設定成本需要 Python、Node 或 jq,且可能需要額外套件只需要一個現代瀏覽器分頁,無其他需求
位元組的傳輸位置若不加上網路步驟,就會留在本機只會留在目前的瀏覽器分頁中
RFC 4180 風格的引號欄位只有 csv-parse 這類解析器做得到;原始的 awk 或 sed 會在引號內切錯由狀態機處理;雙引號會解碼為單一字面引號
型別推斷預設會保留字串,但包裝程式碼可能會強制轉型依合約規定,每個儲存格都保持為 JSON 字串
CI 中的可重現性可腳本化、具確定性、可版本控管手動操作;不適合自動化建置流程
敏感資料在本機上安全;若經管線送往託管服務則不安全安全;任何資料都不會離開瀏覽器分頁

對於乾淨、可腳本化的管線,命令列勝出。對於敏感、引號欄位繁多的一次性轉換,本機瀏覽器工具勝出。兩者之間的折衷方案,是使用 csv-parse 搭配 JSON.stringify 的 Node.js 腳本,這能在 CI 中兼顧 RFC 4180 的正確性,又不必依賴瀏覽器。當腳本所在機器沒有可用的 Node 時,把同一個檔案貼進一個不會上傳的瀏覽器型轉換器,然後在交給管線的下一個階段之前,使用 JSON Formatter 來驗證產生的 JSON。

兩種做法都會遇到的限制

當輸入不再只是單一小表格時,兩種做法都會撞上瓶頸。本機轉換器明確公布了五個上限,一旦觸及便會清楚顯示錯誤訊息,而非默默截斷:

  • 在開始解析前,最多 5,000,000 個 UTF-16 字碼單位的原始輸入。
  • 扣除標頭後,最多 10,000 筆資料列。
  • 總計最多 200 個欄位與 200,000 個資料儲存格。
  • 最多 10,000,000 個 UTF-8 位元組的產生 JSON,其大小以 TextEncoder 衡量,而非 JavaScript 字串長度。

命令列管線則有不同的限制:主機的記憶體、使用者的耐心,以及腳本本身的臭蟲表面。轉換本身不會拒絕檔案,但一個自行撰寫、以逗號切分的 awk 篩選器,只要遇到引號欄位,就會默默產出錯誤的 JSON。對於超出本機瀏覽器上限的大型表格來說,手動切檔再逐一轉換每一段,通常比放寬解析器的限制更為安全。

一個會同時困住兩種做法的微妙限制:第一筆記錄就是標頭。RFC 4180 允許沒有標頭的 CSV,但預期物件輸出的嚴格消費者若沒有欄位名稱就無法繼續。如果你的檔案沒有標頭列,請在貼上或執行 csv.DictReader 之前先補上一列。

這些記錄本身在序列化之前會以無原型的物件形式建立,因此 __proto__、constructor、prototype 與 toString 都只是普通的自有字串鍵,而非繼承而來的行為。JSON.stringify 接著會以兩個空格的縮排,對屬性名稱、引號、反斜線、控制字元以及內嵌的換行字元進行跳脫,因此輸出結果是合法的 JSON 資料,而非可執行的程式碼。任何接收端的應用程式在信任之前,仍須自行解析並驗證輸出。

如果你正在權衡各種選項,駝峰式命名轉蛇形命名:命令列 vs 線上工具有更詳細的說明。