最近测试反馈了一个奇怪的bug:

“编辑当前行的时候,在提交保存之后。未填写数据的部分,自动使用了上一行的值”

排查

接到这个问题的时候,我最开始怀疑是不是前端把数据报错了。于是打开F12,看了一下网络请求。
在进行多次复现尝试后,确认不是前端的问题。于是开始复核后端代码。
在处理数据部分,将代码抽象成以下逻辑,便于表达。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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打印后,发现了问题

打印结果

1
2
3
4
------------user one------------
[{"Card":"A001","Name":"张三","Age":10}]
------------user two------------
[{"Card":"A001","Name":"李四","Age":10}]

嗯?为什么属性没有清空。和期望的 [{"Card":"","Name":"李四","Age":0}] 不一样

那如果我们加上清空Slice尝试呢

尝试清空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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的操作。
执行后日志如下:

1
2
3
4
------------user one------------
[{"Card":"A001","Name":"张三","Age":10}]
------------user two------------
[{"Card":"A001","Name":"李四","Age":10}]

嗯?怎么还不对

换一种清空方式

1
userSlice = userSlice[:0]

更改为

1
userSlice = nil

执行后日志如下:

1
2
3
4
------------user one------------
[{"Card":"A001","Name":"张三","Age":10}]
------------user two------------
[{"Card":"","Name":"李四","Age":0}]

世界终于清净了

问题拓展

那么,Slice有这样的问题,直接Struct呢,会不会也出现这样的问题呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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))
}

执行后日志如下:

1
2
3
4
------------user one------------
{"Card":"A001","Name":"张三","Age":10}
------------user two------------
{"Card":"A001","Name":"李四","Age":10}

涛声依旧

// user = User{} 注释解除
执行后日志如下:

1
2
3
4
------------user one------------
{"Card":"A001","Name":"张三","Age":10}
------------user two------------
{"Card":"","Name":"李四","Age":0}

问题总结

这里不是从GolangSDK源码级别的解释。仅从我们这次发现的bug进行总结。

  1. json.Unmarshal的运行规则,是从字符串中取出值,设置到对应的结构体的同Key上。在此之前,并不会清空该结构体已有的数据
  2. 在Slice执行userSlice = userSlice[:0]这样的操作,并不能释放该Slice所占用的空间(可以使用cap(userSlice)查看)。保守的做法userSlice = nil
  3. 在使用 json.Unmarshal 之前,应重新初始化对应的结构体(Slice、Struct)