如果你在用 Go 語言編程掸驱,并且使用 json.Decoder 反解 JSON 負載,你可能會產(chǎn)生非預期效果没佑。你應該使用 json.Unmarshal 代替 json.Decoder.
- json.Decoder 被設(shè)計用來反解 JSON 流毕贼,而非完整 JSON對象。
- json.Decoder 會忽略某些不合法的 JSON 語法蛤奢。
- 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)衡策略了懈凹,那就自己決定吧。