Question
下面的测试能够通过吗?
func TestJsonUnmarshal(t *testing.T) {
var err error
p := Person{
Name: "zm",
Age: 29,
Sex: 1,
}
t.Run("empty json unmarshal to value struct", func(t *testing.T) {
data := []byte(`{}`)
// notice we unmarhsal data to `p`
err = json.Unmarshal(data, &p)
assert.Nil(t, err)
assert.Equal(t, "", p.Name)
assert.Equal(t, 0, p.Age)
assert.Equal(t, int8(0), p.Sex)
})
}
主要逻辑是将一个空的json字符串{} unmarshal到一个已经有值的Person struct p 上。
事出无常必有妖,既然我这么问了,答案是上面的测试通过不了的。
难道是我对json包的Unmarshal有误解吗?
按照之前的使用情况,我默认空的json字符串应该覆盖struct,就像测试中assert中的那样,所有字段值变成该字段类型的zero value才对。
但与我最初想的完全不同——Unmarshal时只会覆盖有值的字段。
var err error
p := Person{
Name: "zm",
Age: 29,
Sex: 1,
}
t.Run("x", func(t *testing.T) {
// notice name becomes empty; Age 29->30; sex:1->2
data := []byte(`{"Age":30,"Sex":2}`)
err = json.Unmarshal(data, &p)
assert.Nil(t, err)
assert.Equal(t, "zm", p.Name) // right
assert.Equal(t, 30, p.Age) // right
assert.Equal(t, int8(2), p.Sex) // right
})
Dive into
上面描述的情况或许只是当初设计Unmarshal方法时的约定,不算是bug。
dive into就算了,各位如果有兴趣,就留待各位自己去探索吧~
Background
上面是我在开发功能时实际遇到的问题。
简单来说,某些情况下,我们通过下发故障信息到用户的机器,业务代码中集成我们的sdk,sdk中循环获取故障信息,并且规定,发送空字符串{}表示清空故障信息,不再放火。
伪代码如下:
var param Param // 存放放火配置
for {
config := apollo.GetConfig()
// Boom !!!
if len(config) != 0 {
_ = json.Unmarshal(config, ¶m)
}
time.Sleep(5*time.Second)
}
上面的代码不会达到我们预期的结果。
Inspiration
给我们的启示是什么,我觉得至少有两个。
- 每次使用
json.Unmarshal反序列化字符串时,我们一定要使用新创建的struct作为载体,否则很容易出现问题。
让我莫名想起来slice的append操作,最佳实践是使用同名的slice作为append的返回值。
arr = append(arr, 1) // that's fine
append(arr, 2) // oh no, it may be in trouble
- 全局变量,它啊,真的很容易引发问题。
- 单元测试,能够发现很多始料不及的问题。
Reference
滴滴混沌工程项目代码