最近测试反馈了一个奇怪的bug:
“编辑当前行的时候,在提交保存之后。未填写数据的部分,自动使用了上一行的值”
排查
接到这个问题的时候,我最开始怀疑是不是前端把数据报错了。于是打开F12,看了一下网络请求。 在进行多次复现尝试后,确认不是前端的问题。于是开始复核后端代码。 在处理数据部分,将代码抽象成以下逻辑,便于表达。
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Card string
Name string
Age int
}
func main() {
jsonUserOne = []byte(`[{"Card":"A001","Name":"张三","Age":10}]`)
jsonUserTwo = []byte(`[{"Name":"李四"}]`)
var userSlice []User
_ = json.Unmarshal(jsonUserOne, &userSlice)
fmt.Println("------------user one------------")
userOne, _ = json.Marshal(userSlice)
fmt.Println(string(userOne))
// 为了方便,这里直接复用了 userSlice 这个对象
_ = json.Unmarshal(jsonUserTwo, &userSlice)
fmt.Println("------------user two------------")
userTwo, _ = json.Marshal(userSlice)
fmt.Println(string(userTwo))
}
定位问题
第一眼看上去,上面的代码好像没什么问题。可是添加log打印后,发现了问题
打印结果
------------user one------------
[{"Card":"A001","Name":"张三","Age":10}]
------------user two------------
[{"Card":"A001","Name":"李四","Age":10}]
嗯?为什么属性没有清空。和期望的 [{"Card":"","Name":"李四","Age":0}] 不一样
那如果我们加上清空Slice尝试呢
尝试清空
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Card string
Name string
Age int
}
func main() {
jsonUserOne = []byte(`[{"Card":"A001","Name":"张三","Age":10}]`)
jsonUserTwo = []byte(`[{"Name":"李四"}]`)
var userSlice []User
_ = json.Unmarshal(jsonUserOne, &userSlice)
fmt.Println("------------user one------------")
userOne, _ = json.Marshal(userSlice)
fmt.Println(string(userOne))
userSlice = userSlice[:0]
// 为了方便,这里直接复用了 userSlice 这个对象
_ = json.Unmarshal(jsonUserTwo, &userSlice)
fmt.Println("------------user two------------")
userTwo, _ = json.Marshal(userSlice)
fmt.Println(string(userTwo))
}
更改后的代码,添加了 userSlice = userSlice[:0] 清空slice的操作。
执行后日志如下:
------------user one------------
[{"Card":"A001","Name":"张三","Age":10}]
------------user two------------
[{"Card":"A001","Name":"李四","Age":10}]
嗯?怎么还不对
换一种清空方式
将
userSlice = userSlice[:0]
更改为
userSlice = nil
执行后日志如下:
------------user one------------
[{"Card":"A001","Name":"张三","Age":10}]
------------user two------------
[{"Card":"","Name":"李四","Age":0}]
世界终于清净了
问题拓展
那么,Slice有这样的问题,直接Struct呢,会不会也出现这样的问题呢?
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Card string
Name string
Age int
}
func main() {
jsonUserOne := []byte(`{"Card":"A001","Name":"张三","Age":10}`)
jsonUserTwo := []byte(`{"Name":"李四"}`)
var user User
_ = json.Unmarshal(jsonUserOne, &user)
fmt.Println("------------user one------------")
userOne, _ := json.Marshal(user)
fmt.Println(string(userOne))
// user = User{}
_ = json.Unmarshal(jsonUserTwo, &user)
fmt.Println("------------user two------------")
userTwo, _ := json.Marshal(user)
fmt.Println(string(userTwo))
}
执行后日志如下:
------------user one------------
{"Card":"A001","Name":"张三","Age":10}
------------user two------------
{"Card":"A001","Name":"李四","Age":10}
涛声依旧
将 // user = User{} 注释解除
执行后日志如下:
------------user one------------
{"Card":"A001","Name":"张三","Age":10}
------------user two------------
{"Card":"","Name":"李四","Age":0}
问题总结
这里不是从GolangSDK源码级别的解释。仅从我们这次发现的bug进行总结。
json.Unmarshal的运行规则,是从字符串中取出值,设置到对应的结构体的同Key上。在此之前,并不会清空该结构体已有的数据- 在Slice执行
userSlice = userSlice[:0]这样的操作,并不能释放该Slice所占用的空间(可以使用cap(userSlice)查看)。保守的做法userSlice = nil - 在使用
json.Unmarshal之前,应重新初始化对应的结构体(Slice、Struct)