Golang 结构体转换中的omitempty标签
个人学习总结,如有不对的地方也欢迎指出,一起交流学习,共同进步
结构体转Json
type Person1 struct {
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr"`
}
type Person2 struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
Addr string `json:"addr"`
}
func main() {
var ps Person1
//var ps Person2
ps.Name = "ming"
ps.Addr = "Beijing"
psByte, _ := json.Marshal(ps)
fmt.Printf("%s\n", string(psByte))
}
上面两个结构体的Age字段分别不带和带有omitempty标签,如果Age字段都赋值为零值 ,则各自生成的json对象分别对应下面两个结果
Person1: {"name":"ming","age":0,"addr":"Beijing"}
Person2: {"name":"ming","addr":"Beijing"}
Person2相较于Person1得到的json对象中没有"age"键,原因就在于 omitempty标签会在json数据结构转换时,忽略掉了对应类型为零值的字段。
Json转结构体
和结构体转Json对象不同,omitempty并不约束或者说无法约束json对象转结构体,无论有没有添加这个标签,转换得到结构体的所有成员都是可以访问的(前提是成员首字母是大写的)且有值的。
比如说同样是上面的结构体Person1和Person2,当我们用下面的json对象分别去转换成这两个结构体时,会发现它们俩的内容是完全一样的,omitempty并不会影响其结果,这是因为在json.Unmarshal前必须要先初始化出一个结构体对象(即这里的pst1或pst2)才能去承接json对象的值,而结构体对象在默认初始化时每个成员就已经被赋予了一个对应类型的零值了,所以即使结构体内定义的成员找不到json对象中对应的键,每个成员也都是有值的(虽然这就会导致结构体和json对象间可能并不是完全对应的)。
Person: {"name": "ming", "addr": "Beijing"}
既然上面提到,当结构体内定义的成员找不到json对象中对应的键,那么这个结构体成员会被默认赋予一个零值,并不受omitempty影响。那一个由json对象转换得到的结构体对象中的某个成员是零值,如何去判断是原来的json到底有没有这个键呢?
这时候可以把这个字段定义成对应的指针类型,如下面结构体Person3的Addr字段
type Person3 struct {
Name string `json:"name"`
Age int `json:"age"`
Addr *string `json:"addr,omitempty"`
}
虽然json对象不管有没有"addr"这个键,得到的Person3对象都能够访问Addr字段,但可以通过比较Addr是否等于nil来判断json对象存不存在对应键,如果等于nil 则说明不存在,反之则存在。这里其实是利用了指针的零值为nil的特性。
添加omitempty的时机
对于一个指针类型的字段,无论是否添加omitempty字段,在不给该字段传值的情况,默认会被赋予一个空指针,依旧可以对这个结构体对象的该字段判断是否为空
其实就是对于一个结构体的字段而言,是否添加该字段只会影响到该结构体对象转化为json对象,添加了该字段,而恰好创建这个结构体对象时这个字段又没有传入值,那这个字段就会默认被赋予一个零值,然后再转换为json对象的时候这个字段就会被省略掉,使得展开jso对象的时候是看不到这个字段的。但并不会影响到这个结构体对象的使用,结构体的这个字段就是一个零值,类比的你可以想象创建一个空结构体对象的时候,那么所有字段都初始化为对应类型的零值了
下面只考虑结构体转Json的场景
如果字段是int类型,通常都要加上omitempty,因为你不希望明明没给字段赋值生成的json对象还会有这个对应键。同时,要将int转换为*int,否则就会因为赋值也为却被忽略掉的情况,除非这个字段的不存在值为的情况
如果是string类型,没给这个字段传值也没关系,因为本身string的零值也是(空),有没有加上omitempty关键字生成json对象的结果都是一样的。但如果""(空字符串)对于该字段也具有某种含义的话,则需要将string类型转为*string(指针)类型
如果是结构体,需要分两种情况来看,一种是匿名结构体,另一种是有名结构体。
type Book struct {
Name string `json:"name"`
Price float32 `json:"price"`
Desc string `json:"desc,omltempty"`
Author //匿名结构体
}
type Book2 struct {
Name string `json:"name"`
Price float32 `json:"price"`
Desc string `json:"desc,omitempty"`
Author Author `json:"author,omitempty"` //有名结构体
}
type Book3 struct {
Name string `json:"name"`
Price float32 `json:"price"`
Desc string `json:"desc,omitempty"`
Author *Author `json:"author,omitempty"` //有名结构体(指针类型)
}
type Author struct {
Gender int `json:"gender,omitempty"`
Age int `json:"age,Omitempty"`
}
1)匿名结构体本质上更接近外层结构体的字段的一个集合,并不算结构体嵌套。
func main() {
var book Book
book.Name = "testBook"
bookByte, _ := json.Marshal(book)
fmt.Printf("%s\n", string (bookByte))
}
对于上面的定义,在Gender和Age都没有赋值的情况下,输出的结果下面这样,但要注意的是,被忽略掉其实是"gender"和"age'"两个键而非"author"这个键,只要给Gender或者Age字段赋值,"gender"或"age"键就会显示出来,且和"name"、"price" 平级。
{"name":"testBook","price":0}
2)而有名结构体依旧可以按照两种方式去讨论,即原类型和指针类型
func main() {
var book Book
book.Name = "testBook"
bookByte, _ := json.Marshal(book)
fmt.Printf("%s\n", string (bookByte))
}
比如原类型结果是这样
{"name":"testBook","price":0,"author":{}}
但指针类型的结果就会忽略掉"author"键
{"name":"testBook","price":0}
产生这种差别的原因就在于编译器如何处理某一种类型的零值。无论是int、 string、interface、*ptr还是slice、map类型的字段,当没有显式初始化时, 编译器都能默认地给设置一个相应类型的零值,而恰好该字段又有omitempty关键字时,转换到json对象时就会因为这个字段为一个零值忽略掉该字段。
而结构体类型比较特殊,以上面的定义为例,Book2.Author没有显示初始化时, Book2.Author内部所有字段会递归地初始化为各个类型的零值,即结构体的零值为 内部字段均为零值的结构体对象,而在转换的时候,会将整个Book2.Author字段转 换为一个空的嵌套json对象(Gender和Age字段都被忽略掉了),键名则为 "author"。如果希望忽略掉这个键,便需要将Book2.Author的类型变成对应的结构体指针,因为这样就可以按照指针的零值去处理了
总结来说就是下面几点
1)结构体转json时,如果希望某个字段不显式初始化(赋值)就不会存在对应的键,则加上0mitempty关键字
2)如果已经加上了omitempty关键字,但显示初始化(赋值)这个字段的数据又可能为该类型的零值,则需要考虑将这个字段转成对应的指针类型去处理,否则这 个字段即便传入了值,最后仍然会因为是零值而被忽略
3) json转结构体时,某个字段的值是零值(如int类型的字段数值为0),如果需要确定json对象是否存在这样一个键,则可以将这个字段转成对应的指针类型去处理
浙公网安备 33010602011771号