Go encoding json 的几个坑


以前在引入第三包的时候,为了兼容性和定制扩展的考虑,一般会浅浅地封装一层。自从入了 Go 坑,发现连标准库也得小心再小心了。

decode(map) 之后 int 会变成 float64

思考一下,把一个 map encode 之后再 decode,结果和原来的 map 是否相等?直觉上肯定是对的,然而…

看一段代码(https://play.golang.org/p/DHb-kZNHidd):

m := make(map[int]interface{})
m1 := make(map[int]interface{})

m[2] = 3

b, _ := json.Marshal(m)
json.Unmarshal(b, &m1)

fmt.Println(m)	// map[2:3]
fmt.Println(m1)	// map[2:3]
fmt.Println(reflect.DeepEqual(m, m1))	// false

看起来是不是很诡异,打印出来的都一模一样,然而两个却不相等。是不是 DeepEqual 里藏着什么猫腻?按照代码注释, 只要每个元素都相等,整个 map 就相等。

难不成这个2和3有问题?我们再打印一下看看:

fmt.Printf("%T %T", m[2], m1[2])
// int float64

这个时候发现,decode 出来的数字被悄悄地变成了 float64。后来发现,其实官方也早有说明。只是这种不起眼的功能平时没注意。

如果上面例子里改成 m[2] = 3.0,结果就正常了。参见:https://play.golang.org/p/vAC1BXc7nCO

encoding 时自动追加 ‘\n’

Unit Testing 时遇到的。在 github 上看到有人问到过这个问题,官方给的理由是看起来舒服。不得不说,Go Team 在写 std 时真是太随意了。

暂时没想到特别好的解决方案,在自己的工具类 JsonEncode 里手动给去掉了。因为一般使用的 json 末尾都是 } ]。后面或许会加个全局的开关来配置?

默认情况下会开启 escapeHTML

严格来说不能算坑,但确实跟我之前的习惯不太一样。而且要关闭的时候,还不能直接设置,得绕个大弯:

buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)

// 到这才算 init 完成

encoder.Encode(...)

现在很多人使用 Go 只是提供一个简单的 json 给客户端,html 字符,其实不太会搞出什么麻烦,有时候也就懒得处理了。

不能处理递归指针(会死循环)

如果对象的属性是指向自己的指针(例如循环列表),marshal 时会陷入死循环。在 dump 复杂对象时需要注意。

println 函数没有这个问题,因为会把指针直接打印出地址。

最后

建议大家使用的自己也能稍微封装一下,至少在各种特殊场景下能自己掌控住。尤其 Go 很多官方包不像 Java 那样接口先行,而且 Go 还不支持继承,以至于在项目大了后想替换个 struct 非常痛苦。

另外我自己也在尝试对这些问题做一些整理,欢迎加入。

Avatar
huiren
Code Artisan

问渠那得清如许,为有源头活水来

相关

下一页
上一页
comments powered by Disqus