Go 1.27 正式引入 encoding/json/v2,預設拒絕重複欄位與無效 UTF-8

Go 1.27 encoding/json/v2 預設拒絕重複欄位與無效 UTF-8

Go 語言團隊於 2026 年 11 月 4 日在官方部落格宣佈,encoding/json/v2 與 encoding/json/jsontext 兩個新套件已告穩定,並隨 Go 1.27 正式提供 [1]。這套 API 改動了沿用超過十五年的 JSON 序列化預設行為:解碼時遇到無效 UTF-8 或重複的物件欄位名會直接報錯,指標為 nil 的切片與映射則改為輸出空陣列與空物件。官方同時指出,v2 的解碼效能最高可比舊版快十倍。

為何另開一個 v2 命名空間

Go 自 1.0 之前已在標準庫支援 JSON,encoding/json 的設計以反射為基礎,讓任意 Go 型別可以帶預設表示,並容許自訂。多年來社群指出不少缺陷,但部分問題與現有 API 綁得太緊,在 Go 1 相容承諾之下難以直接修正。

官方列出的行為問題包括:接受無效 UTF-8、接受重複欄位名、大小寫不敏感的欄位匹配,以及指標接收者的 MarshalJSON 方法呼叫不一致。最後一項雖然被視為錯誤,但由於太多應用依賴現況,已經無法更改。與其在同一套件內開出 MarshalV2 之類的平行命名,團隊選擇另立 encoding/json/v2,讓新行為可以在獨立命名空間中定義。

新結構將語法與語意分開。jsontext 只負責 JSON 文法層面的編碼與解碼,不依賴反射,並以串流方式運作;v2 則建立於其上,負責 Go 值與 JSON 之間的對應 [2]。RawMessage 現已成為 jsontext.Value 的型別別名。

預設行為的實際差異

從 v1 直接遷移到 v2,大部分用法仍然相似,但以下幾項預設會令既有程式出現新錯誤,值得在升級前逐一檢查。

第一,v2 遇到無效 UTF-8 會報錯,而舊版會默默接受,可能在 downstream 造成資料損壞。第二,物件出現重複欄位名時會報錯;官方特別提到,重複名稱在 RFC 8259 中並無一致解釋,歷史上曾被利用作攻擊,例如 CVE-2017-12635。第三,nil 切片與映射會序列化為 [] 與 {},不再輸出 null。第四,解碼時欄位名稱改為大小寫敏感匹配。

此外,omitempty 的語意被重新定義:只有當欄位會編碼成 null、空字串、空陣列或空物件時才省略。time.Duration 因缺乏公認的預設表示,直接序列化會報錯,需由呼叫方以選項指定格式。官方表示,大部分行為差異都提供 struct tag 或呼叫層級選項,可切換回 v1 語意。

選項、串流與升級注意

v2 的 Marshal 與 Unmarshal 接受可變選項,解決了舊版只有 Encoder、Decoder 才能設定選項的限制。新增的 MarshalWrite 與 UnmarshalRead 可直接對 io.Writer 及 io.Reader 運作,避免為了讀寫而臨時建立編碼器。

自訂型別方面,除沿用 Marshaler 與 Unmarshaler,亦新增 MarshalerTo 與 UnmarshalerFrom,讓實作可以透過 jsontext.Encoder 或 Decoder 以串流方式處理,並把選項沿呼叫堆疊傳遞。官方指出,若要取得更明顯的效能提升,現有的自訂方法應一併遷移至新介面;單就解碼而言,基準測試顯示改善可達十倍。

這個項目由 2020 年底開始構思,2025 年在 Go 1.25 以實驗形式開放,最終於 1.27 轉為穩定。官方強調,大部分開發與推動工作由非 Google 僱員完成。對香港的 Go 服務團隊而言,短期內不宜全面替換所有 encoding/json 匯入;應先針對公開 API 的輸入驗證路徑試行,因重複欄位與 UTF-8 檢查正是最可能引發行為改變的地方,並參考官方遷移指南逐項調整 [3]。

參考資料

[1] A new Go API for JSON(2026-11-04)

[2] encoding/json/v2 套件文件

[3] encoding/json/v2 遷移指南

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

*