在golang里经常需要将数据在struct对象和json串之间转换。
最简单的序列化
序列化(从golang对象转换为JSON串)只需要对结构体进行标注即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 对struct进行标注
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}
// 序列化
if buf, err := json.Marshal(User{
Name: "seedjyh",
Email: "seedjyh@gmail.com",
Password: "123456",
}); err != nil {
fmt.Println(string(buf))
// 输出 {"name":"seedjyh","email":"seedjyh@gmail.com","password":"123456"}
}
|
很简单不是吗?
追加JSON键
然而,如果需要将User
对象传递给服务器时,服务器需要追加一个current
键值对,以存储发送时刻,那该怎么办?
方法1:直接修改原结构体
直接往User结构体里添加一个Current字段,几下敲完。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
Current time.Time `json:"current"` // 加了这一行
}
// 序列化
if buf, err := json.Marshal(User{
Name: "seedjyh",
Email: "seedjyh@gmail.com",
Password: "123456",
Current: time.Now(),
}); err != nil {
fmt.Println(string(buf))
// 输出 {"name":"seedjyh","email":"seedjyh@gmail.com","password":"123456","current":"2020-03-12T14:55:42.7560612+08:00"}
}
|
看上去不错,但是这个Current
字段会影响所有用到这个User结构的地方。初始化的时候麻烦,序列化(而不需要Current字段)的地方更麻烦。
有更好的方法吗?
方法2:定义一个新的结构体
1
2
3
4
5
6
|
type UserWithCurrent struct {
Name string `json:"name"` // 照抄
Email string `json:"email"` // 照抄
Password string `json:"password"` // 照抄
Current time.Time `json:"current"` // 新增字段
}
|
这比方法1好一些。只在序列化的地方定义UserWithCurrent
结构,其他代码就不受干扰了。
不过还是有两个缺点:
- 构造UserWithCurrent对象时,需要将User对象的每个字段逐一赋值,麻烦又易错。
- 如果原来的User对象增加了字段(比如Age),这里UserWithCurrent也得跟着改。
有更好的方法吗?
方法3:结构体组合
golang没有提供面向对象的继承功能,但提供了一个用组合实现继承的方法。
1
2
3
4
|
type UserWithCurrent struct {
*User // 不加星号也可以,加星号可以节约内存开销
Current time.Time `json:"current"` // 新增字段
}
|
这种结构的UserWithCurrent不但能实现添加current字段的要求,在用User对象构造UserWithCurrent对象时,可以直接将User对象赋值过去而不需对User对象的字段逐个赋值。如下所示:
1
2
3
4
|
userToSend := UserWithCurrent{
User: &user, // user是User结构体的对象。
Current: time.Now(),
}
|
隐藏JSON键
现在有个需求:将User对象(最初的那个struct)的内容按照JSON格式打印到日志里。但是由于Password是敏感字段,需要在打印时去掉。也就是原来的Name、Email、Password三个字段只打印前两个。
是去掉一个键,而不是新增。该怎么办?
方法1:定义一个新的结构体!
1
2
3
4
5
|
type UserWithoutPassword struct {
Name string `json:"name"` // 照抄
Email string `json:"email"` // 照抄
// Password去掉了。
}
|
然后在构造这个对象时逐个从user对象拷贝字段,User字段发生变化时也得连锁修改UserWithoutPassword结构。麻烦,易错。
只有这个方法吗?
并不是。
方法2:结构体组合+重名标注
用结构体组合,依然可以实现字段
1
2
3
4
5
6
7
8
9
|
type UserWithoutPassword struct {
*User
Abc int `json:"password,omitempty"` // 关键是标注password和omitempty
}
userWithoutPassword := UserWithoutPassword{
User: &user,
// 字段Abc留空,不赋值
}
|
稍微解释一下:
UserWithoutPassword里新增的一行里,字段名Abc
和字段类型int
都可以随便写,关键是标注。
Abc的标注为password
,和User对象里的Password字段的标注相同。此时,在json包对对象进行序列化时,JSON的password
键的值只看外层的struct字段。
也就是说,最后序列化的时候,password
键的值由UserWithoutPassword的Abc字段决定。
然后在Abc的标注里加上omitempty
,并在构造UserWithoutPassword对象时故意不对Abc赋值(让其保持默认的空值),即可在最终生成的JSON串里隐藏password这个JSON键。
重命名JSON键
还是原来那个User结构,现在要求name
键改成username
键。
其实也很简单,联合使用上面的“追加JSON键”和“隐藏JSON键”即可。
1
2
3
4
5
6
7
8
9
10
11
|
type UserRenameKey struct {
*User
Abc int `json:"name,omitempty"` // 覆盖键name
Def string `json:"username"` // 新键username
}
userRenameKey := UserRenameKey{
User: &user,
// 字段Abc留空,不赋值
Def: user.Name, // 将user对象里的Name字段赋值给Def字段,最后序列化成username
}
|
参考资料