Go语言匿名字段使用与注意事项
1. 定义
Go语言支持一种特殊的字段只需要提供类型而不需要写字段名
的字段,称之为匿名字段
或者嵌套字段
。
所谓匿名字段
实际上是一种结构体嵌套
的方式,所以也可以称作嵌套字段
。
这种方式可以实现组合复用,即通过匿名字段,结构体可以直接访问嵌套结构体的字段和方法,而无需通过字段名或类型进行嵌套。这些方法和属性被称为“提升”的方法和属性。通过类型名称也可以直接访问匿名嵌入字段。
2.代码示例
2.1 简单示例
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
Phone string
}
func (p *Person) playBasketball() {
fmt.Println("打篮球...")
}
type Employee struct {
Person
EmployeeId int
}
// 测试匿名字段
func TestAnonymous() {
emp := Employee{
Person: Person{
Name: "Liu",
Age: 20,
Phone: "18899999999",
},
EmployeeId: 101,
}
// 可直接使用emp调用嵌套类型的方法
emp.playBasketball()
fmt.Println("id: ", emp.EmployeeId)
// 可直接使用emp打印出嵌套类型的所有字段
fmt.Println("name: " + emp.Name)
fmt.Println("age: ", emp.Age)
fmt.Println("name: " + emp.Phone)
// 通过匿名类型名来访问
fmt.Println("类型访问的id: ", emp.Person.Name)
}
func main() {
TestAnonymous()
}
2.2 嵌套类型有重复字段
在上面的例子中,Employee
结构体嵌套了 Person
结构体,通过这种方式,Employee
可以直接访问 Person
的字段和方法,而无需使用类似 emp.Person.Name
这样的方式。
需要注意的是,如果结构体中有多个 匿名字段,并且它们拥有相同的字段名,那么在访问这个同名字段时,需要指定嵌套结构体的类型,以避免歧义。例如:
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
Phone string
}
func (p *Person) contact() {
fmt.Println("Person联系...")
}
// 合同
type Contract struct {
EmployeeId int
Phone string
}
func (p *Contract) contact() {
fmt.Println("Contract联系...")
}
type Employee struct {
Person
EmployeeId int
Contract
}
// 测试匿名字段
func TestAnonymous() {
emp := Employee{
Person: Person{
Name: "Liu",
Age: 20,
Phone: "18899999999",
},
EmployeeId: 101,
Contract: Contract{
EmployeeId: 101,
Phone: "16699999999",
},
}
// 多个匿名类型字段的字段名称可以相同,这样在访问时需要通过匿名类型名来访问
//emp.contact() //会报错
emp.Person.contact()
emp.Contract.contact()
fmt.Println("id: ", emp.EmployeeId)
// 可直接使用emp打印出嵌套类型的所有字段
fmt.Println("name: " + emp.Name)
fmt.Println("age: ", emp.Age)
//fmt.Println("name: " + emp.Phone)//这里会报错,因为Person和Contract中都有Phone字段
fmt.Println("person phone: ", emp.Person.Phone)
fmt.Println("contract phone: ", emp.Contract.Phone)
}
func main() {
TestAnonymous()
}
在这个例子中,Person 和 Contract 都有 Phone 字段,因此在访问时需要指定具体的类型以避免歧义。
同样的,Person 和 Contact 都有 contact 方法,因此在访问时也需要指定具体的类型以避免歧义。
如果不指定则会编译报错.
3. 结构体匿名字段的Json序列化、反序列化
结构体序列化规则
@注意:可导出的字段(首字母大写),才能参与Json的序列化
标签 | json的key |
---|---|
有标签,json:"xx" | key=xx |
无标签 | key=结构体原属性字段 |
有标签,json:"-" | 会被忽略,不参与序列化 |
有标签,json:"xxx,omitempty" | 代表该字段为空值时,会被忽略。其中xxx 可以省略,, 不可以省略。如: json:",omitempty" |
有标签,json:"xxx,string" | 代表输出类型会转化为字符串。其中xxx 也可以省略它只适用于字符串、浮点数、整数类型的字段 |
3.1 代码示例
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
// 指定json标签时,序列化的key为标签值:name
Name string `json:"name"`
// 不指定序列化标签时,key为原属性:Age
Age int
// 当标签值为`json:"-"`,代表改字段会被忽略
Home string `json:"-"`
// 标签指定omitempty选项,代表该字段为空值时,会被忽略
Phone string `json:"phone,omitempty"`
// 标签指定string选项,代表输出类型会转化为字符串
// 它只适用于字符串、浮点数、整数类型的字段
Score float64 `json:"score,string"`
}
func TestMarshal() {
// 声明初始化结构体
student1 := Student{
Name: "Liu",
Age: 18,
Home: "北京",
Score: 90.5,
Phone: "",
}
// 序列化
json1, _ := json.Marshal(student1)
fmt.Printf("序列化json:%s\n", json1)
}
func main() {
TestMarshal()
}
输出结果:
序列化json:{"name":"Liu","Age":18,"score":"90.5"}
3.2 匿名字段序列化
3.2.1 无JSON
标签
a. 字段标签不重复
School.Name和Student.Name,Json标签不一致。
package main
import (
"encoding/json"
"fmt"
)
// 学校
type School struct {
Name string `json:"schoolName"`
Address string `json:"schoolAddress"`
}
// 学生
type Student struct {
Name string `json:"name"`
// 匿名字段,而且没有json标签
School
}
// 序列化-匿名字段 (默认字段不冲突)
func TestAnonymousTagDifferent() {
var student = Student{
Name: "XiaoMing",
School: School{
Name: "北京大学",
Address: "北京海淀区",
},
}
jsonByte, _ := json.Marshal(student)
fmt.Printf("json: %s \n", jsonByte)
}
func main() {
TestAnonymousTagDifferent()
}
结果:
json: {"name":"XiaoMing","schoolName":"北京大学","schoolAddress":"北京海淀区"}
b. 字段标签重复
School.Name和Student.Name,Json标签一致,都是 json:"name"
。
package main
import (
"encoding/json"
"fmt"
)
// 学校
type School struct {
Name string `json:"name"`
Address string `json:"schoolAddress"`
}
// 学生
type Student struct {
Name string `json:"name"`
// 匿名字段,而且没有json标签
School
}
// 序列化-匿名字段 (默认字段冲突)
func TestAnonymousTagDifferent() {
var student = Student{
Name: "XiaoMing",
School: School{
Name: "北京大学",
Address: "北京海淀区",
},
}
jsonByte, _ := json.Marshal(student)
fmt.Printf("json: %s \n", jsonByte)
}
func main() {
TestAnonymousTagDifferent()
}
结果:
json: {"name":"XiaoMing","schoolAddress":"北京海淀区"}
根据上面代码,得知如果字段标签冲突,冲突的匿名字段会被忽略。
3.2.2 有JSON
标签
当匿名字段设置json
标签时, 就不会出现冲突的情况,因为序列化后的匿名字段会变成对象。
package main
import (
"encoding/json"
"fmt"
)
// 学校
type School struct {
Name string `json:"name"`
Address string `json:"schoolAddress"`
}
// 学生
type Student struct {
Name string `json:"name"`
// 匿名字段,而且没有json标签
School `json:"school"`
}
// 序列化-匿名字段 (默认字段冲突)
func TestAnonymousTagDifferent() {
var student = Student{
Name: "XiaoMing",
School: School{
Name: "北京大学",
Address: "北京海淀区",
},
}
jsonByte, _ := json.Marshal(student)
fmt.Printf("json: %s \n", jsonByte)
}
func main() {
TestAnonymousTagDifferent()
}
结果:
json: {"name":"XiaoMing","school":{"name":"北京大学","schoolAddress":"北京海淀区"}}
对比前面两个代码可以发现 当匿名字段设置json
标签时,序列化后的匿名字段会变成对象
3.3 匿名字段反序列化
3.3.1 无JSON
标签
a. 字段标签不重复
package main
import (
"encoding/json"
"fmt"
)
// 学校
type School struct {
Name string `json:"schoolName"`
Address string `json:"schoolAddress"`
}
// 学生
type Student struct {
Name string `json:"name"`
// 匿名字段,而且没有json标签
School
}
// 反序列化-匿名字段 (默认字段不冲突)
func TestUnMarshal() {
jsonStr := `{"name":"XiaoMing","schoolName":"北京大学","schoolAddress":"北京海淀区"}`
stu := Student{}
err := json.Unmarshal([]byte(jsonStr), &stu)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("反序列化结果:%+v", stu)
fmt.Println()
}
func main() {
TestUnMarshal()
}
结果:
反序列化结果:{Name:XiaoMing School:{Name:北京大学 Address:北京海淀区}}
b. 字段标签重复
package main
import (
"encoding/json"
"fmt"
)
// 学校
type School struct {
Name string `json:"name"`
Address string `json:"schoolAddress"`
}
// 学生
type Student struct {
Name string `json:"name"`
// 匿名字段,而且没有json标签
School
}
// 反序列化-匿名字段 (默认字段冲突)
func TestUnMarshal() {
jsonStr := `{"name":"XiaoMing","schoolAddress":"北京海淀区"}`
stu := Student{}
err := json.Unmarshal([]byte(jsonStr), &stu)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("反序列化结果:%+v", stu)
fmt.Println()
}
func main() {
TestUnMarshal()
}
结果:
反序列化结果:{Name:XiaoMing School:{Name: Address:北京海淀区}}
其中如果上面示例中将jsonStr改为如下
jsonStr := `{"name":"XiaoMing","name":"北京大学","schoolAddress":"北京海淀区"}`
那么结果如下:可以看到Name的值变了,但是School中的依然没有值
反序列化结果:{Name:北京大学 School:{Name: Address:北京海淀区}}
从上面示例中可以看到 当字段标签重复时,反序列化会优先给主属性字段赋值。
3.3.2 有JSON
标签
示例代码:
package main
import (
"encoding/json"
"fmt"
)
// 学校
type School struct {
Name string `json:"name"`
Address string `json:"schoolAddress"`
}
// 学生
type Student struct {
Name string `json:"name"`
// 匿名字段,而且没有json标签
School `json:"school"`
}
// 反序列化-匿名字段 (默认字段冲突)
func TestUnMarshal() {
jsonStr := `{"name":"XiaoMing","name":"北京大学","schoolAddress":"北京海淀区"}`
stu := Student{}
err := json.Unmarshal([]byte(jsonStr), &stu)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("反序列化结果:%+v", stu)
fmt.Println()
jsonStr2 := `{"name":"XiaoMing","school":{"name":"北京大学","schoolAddress":"北京海淀区"}} `
stu2 := Student{}
err = json.Unmarshal([]byte(jsonStr2), &stu2)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("2反序列化结果:%+v", stu2)
fmt.Println()
}
func main() {
TestUnMarshal()
}
结果:
反序列化结果:{Name:北京大学 School:{Name: Address:}}
2反序列化结果:{Name:XiaoMing School:{Name:北京大学 Address:北京海淀区}}
3.4 匿名字段json
总结
3.4.1 序列化
a. 匿名字段无标签
-
当匿名字段没有指定标签时,序列化后的结构为同级,如
{"..":"..",..}
-
当匿名属性和主属性的字段标签一样时,序列化会忽略匿名属性的字段。
-
当匿名属性和主属性的字段标签不一样时,序列化不忽略任何字段。
b. 匿名字段有标签
-
当匿名字段
a
指定标签时,序列化后的结构为上下级,如{"..":"..","a":{"xx":"xx"}}
-
匿名属性和主属性字段,标签无论是否一致,序列化都不会忽略任何字段。
4.2 反序列化
a. 匿名字段无标签
-
当匿名字段没有指定标签时,可解析的
JSON
结构,为:{"..":"..",..}
-
当匿名属性和主属性的字段标签一样时,会优先将值赋给主属性,匿名属性为类型零值。
-
当匿名属性和主属性的字段标签不一样时,会正常解析。
b. 匿名字段有标签
-
当匿名字段指定标签时,可解析的
JSON
结构,为:{"..":"..","a":{"xx":"xx"}}
-
匿名属性和主属性字段,标签无论是否一致,反序列化都能正常赋值。
当结构体中嵌套匿名结构体字段时,在进行序列化和反序列时,推荐为匿名字段加上json标签。