如果你在用 Go 语言编程,并且使用 json.Decoder 反解 JSON 负载,你可能会产生非预期效果。你应该使用 json.Unmarshal 代替 json.Decoder.
- json.Decoder 被设计用来反解 JSON 流,而非完整 JSON对象。
- json.Decoder 会忽略某些不合法的 JSON 语法。
- json.Decoder 没有释放网络连接用来重用(会导致拖慢 HTTP 请求到大约4倍时长)。
如果你度过了 json 包的文档,你不会诧异,确实如此。我已经搞错好多次了。大部分开发者发现使用 json.Decoder.Decode(...) 比使用 json.Unmarshal(...) 更方便解析 io.Reader 类型。
1. json.Decoder 为 JSON 流设计
JSON 流一般是串联的(concatenated)或以新行分割的 JSON 值。下面是一个例子:
{"Name":"Ed"}{"Name":"Sam"}{"Name":"Bob"}
完整的流内容并不是一个合法的 JSON, 只有最外层用 [ ]
包围时才是合法的 JSON 类型。
这只是串联的 JSON 对象,换句话说,它是合法的 JSON 流。
json.Decoder 类型被专门设计用以 JSON 流。最有可能的事,你的 JSON 负载并不适用于此。
那么 JSON 流为什么会存在?难道我们不能使用 JSON 数组?JSON 流主要用在:
- 在文件中存储结构化数据,并且在无需完全解析整个文件的情况下快速追加
- 从 API 等实时结构化流式数据(如
docker logs
/docker events
API等就是用此方法)
如果你是在解析单一完整的 JSON 对象,不要使用 json.Decoder。
2. json.Decoder 会忽略不合法语法
并非忽略掉所有不合法的语法,但是混合不合法和合法语法的 JSON 流会被 json.Decoder 忽略错误。 例如假设一个 API 返回:
{"Name": "Bob"}
但是服务引入了 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 对象, 剩余部分忽略掉了。
这可能发生吗?或许不会,但是你能 100% 确定吗?因为当发生的时候你不能容易 debug 出来。
3. json.Decoder 没有正确耗尽 HTTP 连接
这个问题最近由 Flippo Valsorda 提出来(link), 你可能会因此受到影响,除非你在使用 Go 1.7 (or above,译者注)。
如果你正在创造一个 HTTP 请求,传输返回体到 json.Decoder#Decode() (大部分人会这样做做)然后极有可能你的连接没有被正确耗尽,可能使你的 HTTP 客户端变慢4倍。
如果 HTTP 端点返回单一完整的 JSON 对象,并且你只调用 json.Decoder#Decode() 一次,这可能意味着你没有读取到 io.EOF 返回信号。因此你没有根据 io.EOF 终结 json.Decoder,返回体依然是开放中,TCP 连接(或者其他正在使用的 Transport)不能被返回到连接池中即使你已经读完了。 了解更多请点击URL。
现在在 golang 主分支master中已经修复了, 最可能将在 Go1.7 中发布。(这篇文章写于2016年4月28日,彼时尚未发布 Go1.7)
同时,如果你的返回体足够小,只需要用 ioutil.ReadAll
全部读入内存并且使用 json.Unmarshal 反解。如果你想继续使用 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()
}
结论
如果你没有处理 JSON 流,请不要使用 json.Decoder。
使用 json.Unmarshal:
- 如果你不知道 Go JSON 流是什么
- 如果你正在处理单一 JSON 对象
- 如果远程 API 有可能返回有问题的 JSON
现在你知道权衡策略了,那就自己决定吧。
有疑问加站长微信联系(非本文作者)