Go json.Decoder Considered Harmful

如果你在用 Go 語言編程掸驱,并且使用 json.Decoder 反解 JSON 負載,你可能會產(chǎn)生非預期效果没佑。你應該使用 json.Unmarshal 代替 json.Decoder.

  1. json.Decoder 被設(shè)計用來反解 JSON 流毕贼,而非完整 JSON對象。
  2. json.Decoder 會忽略某些不合法的 JSON 語法蛤奢。
  3. json.Decoder 沒有釋放網(wǎng)絡(luò)連接用來重用(會導致拖慢 HTTP 請求到大約4倍時長)鬼癣。

如果你度過了 json 包的文檔,你不會詫異啤贩,確實如此待秃。我已經(jīng)搞錯好多次了。大部分開發(fā)者發(fā)現(xiàn)使用 json.Decoder.Decode(...) 比使用 json.Unmarshal(...) 更方便解析 io.Reader 類型痹屹。

1. json.Decoder 為 JSON 流設(shè)計

JSON 流一般是串聯(lián)的(concatenated)或以新行分割的 JSON 值。下面是一個例子:

{"Name":"Ed"}{"Name":"Sam"}{"Name":"Bob"}

完整的流內(nèi)容并不是一個合法的 JSON志衍, 只有最外層用 [ ]包圍時才是合法的 JSON 類型暖庄。

這只是串聯(lián)的 JSON 對象,換句話說楼肪,它是合法的 JSON 流培廓。

json.Decoder 類型被專門設(shè)計用以 JSON 流。最有可能的事春叫,你的 JSON 負載并不適用于此肩钠。

那么 JSON 流為什么會存在泣港?難道我們不能使用 JSON 數(shù)組?JSON 流主要用在:

  • 在文件中存儲結(jié)構(gòu)化數(shù)據(jù)价匠,并且在無需完全解析整個文件的情況下快速追加
  • 從 API 等實時結(jié)構(gòu)化流式數(shù)據(jù)(如 docker logs/docker events API等就是用此方法)

如果你是在解析單一完整的 JSON 對象当纱,不要使用 json.Decoder。

2. json.Decoder 會忽略不合法語法

并非忽略掉所有不合法的語法霞怀,但是混合不合法和合法語法的 JSON 流會被 json.Decoder 忽略錯誤惫东。 例如假設(shè)一個 API 返回:

{"Name": "Bob"}

但是服務(wù)引入了 bug, 突然開始返回

{}{"Name": "Bob"}

這明顯是不合法的 JSON 負載莉给,但是是一個合法的 JSON 流毙石,json.Decoder 可以接受。

但是你不知道這種情況颓遏,你的代碼會將這個返回反解為完整的 JSON 對象:

type Person struct {Name string}

...
var v Person
if err := dec.Decode(&v); err != nil {
    panic(err)
}
fmt.Println(v.Name)

你就會得到 v.Name 為空字符串徐矩,沒有錯誤。json.Decoder 反解了第一個 JSON 對象, 剩余部分忽略掉了叁幢。

這可能發(fā)生嗎滤灯?或許不會,但是你能 100% 確定嗎曼玩?因為當發(fā)生的時候你不能容易 debug 出來鳞骤。

3. json.Decoder 沒有正確耗盡 HTTP 連接

這個問題最近由 Flippo Valsorda 提出來(link), 你可能會因此受到影響,除非你在使用 Go 1.7 (or above黍判,譯者注)豫尽。

如果你正在創(chuàng)造一個 HTTP 請求,傳輸返回體到 json.Decoder#Decode() (大部分人會這樣做做)然后極有可能你的連接沒有被正確耗盡顷帖,可能使你的 HTTP 客戶端變慢4倍美旧。

如果 HTTP 端點返回單一完整的 JSON 對象,并且你只調(diào)用 json.Decoder#Decode() 一次贬墩,這可能意味著你沒有讀取到 io.EOF 返回信號榴嗅。因此你沒有根據(jù) io.EOF 終結(jié) json.Decoder,返回體依然是開放中陶舞,TCP 連接(或者其他正在使用的 Transport)不能被返回到連接池中即使你已經(jīng)讀完了嗽测。 了解更多請點擊URL

現(xiàn)在在 golang 主分支master中已經(jīng)修復了, 最可能將在 Go1.7 中發(fā)布肿孵。(這篇文章寫于2016年4月28日唠粥,彼時尚未發(fā)布 Go1.7)

同時,如果你的返回體足夠小颁井,只需要用 ioutil.ReadAll 全部讀入內(nèi)存并且使用 json.Unmarshal 反解厅贪。如果你想繼續(xù)使用 json.Decoder, 你需要耗盡返回體中未讀完的部分雅宾,例如:

io.Copy(ioutil.Discard, resp.Body)

因此养涮,如果你正在使用 json.Decoder, 請檢查你的代碼葵硕,將所有 defer resp.Body.Close() 替換為:

defer func() {
    io.Copy(ioutil.Discard, resp.Body)
    resp.Body.Close()
}

結(jié)論

如果你沒有處理 JSON 流,請不要使用 json.Decoder贯吓。

使用 json.Unmarshal:

  • 如果你不知道 Go JSON 流是什么
  • 如果你正在處理單一 JSON 對象
  • 如果遠程 API 有可能返回有問題的 JSON

現(xiàn)在你知道權(quán)衡策略了懈凹,那就自己決定吧。

參考資料

https://ahmet.im/blog/golang-json-decoder-pitfalls/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末悄谐,一起剝皮案震驚了整個濱河市介评,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌爬舰,老刑警劉巖们陆,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異情屹,居然都是意外死亡坪仇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門垃你,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椅文,“玉大人,你說我怎么就攤上這事惜颇〗源蹋” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵凌摄,是天一觀的道長羡蛾。 經(jīng)常有香客問我,道長望伦,這世上最難降的妖魔是什么林说? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮屯伞,結(jié)果婚禮上腿箩,老公的妹妹穿的比我還像新娘。我一直安慰自己劣摇,他們只是感情好珠移,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著末融,像睡著了一般钧惧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勾习,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天浓瞪,我揣著相機與錄音,去河邊找鬼巧婶。 笑死乾颁,一個胖子當著我的面吹牛涂乌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播英岭,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼湾盒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了诅妹?” 一聲冷哼從身側(cè)響起罚勾,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吭狡,沒想到半個月后尖殃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡赵刑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年分衫,在試婚紗的時候發(fā)現(xiàn)自己被綠了场刑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片般此。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牵现,靈堂內(nèi)的尸體忽然破棺而出铐懊,到底是詐尸還是另有隱情,我是刑警寧澤瞎疼,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布科乎,位于F島的核電站,受9級特大地震影響贼急,放射性物質(zhì)發(fā)生泄漏茅茂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一太抓、第九天 我趴在偏房一處隱蔽的房頂上張望空闲。 院中可真熱鬧,春花似錦走敌、人聲如沸碴倾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跌榔。三九已至,卻和暖如春捶障,著一層夾襖步出監(jiān)牢的瞬間僧须,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工项炼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留担平,地道東北人柑蛇。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像驱闷,于是被迫代替她去往敵國和親耻台。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容