Go的Struct结构体和(Json Form tag)
1.Struct
1.1 定义
使用struct关键字可以定义一个结构体,结构体中的成员,称为结构体的字段或属性。
type Member struct {
id int
name, email string
gender, age int
}
type Member struct {
id int
name string
email string
gender int
age int
}
type 和 struct 都是关键字,type表示类型,struct表示结构体。
说明结构体是一个复合类型。
上面的代码中,我们定义了一个包含5个字段的结构体,可以看到,相同类型name
和email
、gender
和age
在同一行中定义,但比较好的编程习惯是每一行只定义一个字段。
1.2 声明
直接定义变量,这个使用方式并没有为字段赋初始值,因此所有字段都会被自动赋予自已类型的零值,比如name
的值为空字符串"",age的值为0。
var m1 Member//所有字段均为空值
var m2 = Member{1,"小明","[email protected]",1,18} // 简短变量声明方式:m2 := Member{1,"小明","[email protected]",1,18}
var m3 = Member{id:2,"name":"小红"}// 简短变量声明方式:m3 := Member{id:2,"name":"小红"}
另一个实例
func Older(p1, p2 Person) (person , int){
if p1.age > p2.age {
return p1, p1.age - p2.age
}
return p2, p1.age - p2.age
}
func main(){
var tom Person
tom.name, tom.age = "tom", 20
bob := Person{name: "bob", age: 22}
old, diff := Older(tom, bob)
fmt.Printf("%s %d", old.name, diff)
}
1.3 访问字段
通过变量名,使用逗号(.)
,可以访问结构体类型中的字段,或为字段赋值,也可以对字段进行取址(&)操作。
fmt.Println(m2.name)//输出:小明
m3.name = "小花"
fmt.Println(m3.name)//输出:小花
age := &m3.age
*age = 20
fmt.Println(m3.age)//20
1.4 指针结构体
另一个实例
type Command struct {
Name string // 指令名称
Var *int // 指令绑定的变量
Comment string // 指令的注释
}
var version int = 1
cmd := &Command{}
cmd.Name = "version"
cmd.Var = &version
cmd.Comment = "show version"
1.5 可见性
上面的例子中,我们定义结构体字段名首字母是小写的,这意味着这些字段在包外不可见
,因而无法在其他包中被访问,只允许包内访问。
下面的例子中,我们将Member声明在member包中,而后在main包中创建一个变量,但由于结构体的字段包外不可见,因此无法为字段赋初始值,无法按字段还是按索引赋值,都会引发panic错误
package member
type Member struct {
id int
name string
email string
gender int
age int
}
package main
fun main(){
var m = member.Member{1,"小明","[email protected]",1,18}//会引发panic错误
}
1.6 Tag
1.7 特点
1.8 方法
1.9 组合
组合,可以理解为定义一个结构体中,其字段可以是其他的结构体,这样,不同的结构体就可以共用相同的字段。
type Animal struct {
Name string //名称
Color string //颜色
Height float32 //身高
Weight float32 //体重
Age int //年龄
}
//奔跑
func (a Animal)Run() {
fmt.Println(a.Name + "is running")
}
//吃东西
func (a Animal)Eat() {
fmt.Println(a.Name + "is eating")
}
type Cat struct {
a Animal
}
func main() {
var c = Cat{
a: Animal{
Name: "猫猫",
Color: "橙色",
Weight: 10,
Height: 30,
Age: 5,
},
}
fmt.Println(c.a.Name)
c.a.Run()
}
可以看到,我们定义Cat结构体时,可以把Animal结构体作为Cat的字段。
另一个实例
//Animal 动物
type Animal struct {
name string
}
func (a *Animal) move() {
fmt.Printf("%s会动!\n", a.name)
}
//Dog 狗
type Dog struct {
Feet int8
*Animal //通过嵌套匿名结构体实现继承
}
func (d *Dog) wang() {
fmt.Printf("%s会汪汪汪~\n", d.name)
}
func main() {
d1 := &Dog{
Feet: 4,
Animal: &Animal{ //注意嵌套的是结构体指针
name: "乐乐",
},
}
d1.wang() //乐乐会汪汪汪~
d1.move() //乐乐会动!
}
1.9.1 匿名字段
当我们创建结构体时,字段可以只有类型,而没有字段名。这样的字段称为匿名字段(Anonymous Field)。习惯上匿名字段叫内嵌,具名字段叫组合.
上面的例子,我们看到,把Animal结构体作为Cat的字段时,其变量名为a,所以我们访问Animal的方法时,语法为c.a.Run()
,这种通过叶子属性访问某个字段类型所带的方法和字段用法非常繁琐。
Go语言支持直接将类型作为结构体的字段,而不需要取变量名,这种字段叫匿名字段
,如:
type Lion struct {
Animal //匿名字段
}
func main(){
var lion = Lion{
Animal{
Name: "小狮子",
Color: "灰色",
},
}
lion.Run()
fmt.Println(lion.Name)
}
通过上面例子,可以看到,通过匿名字段组合其他类型,而后访问匿名字段类型所带的方法和字段时,不需要使用叶子属性,非常方便。
结构体允许其成员字段在声明时只有类型没有字段名,这种没有名字的字段就称为匿名字段。
//Person 结构体Person类型
type Person struct {
string
int
}
func main() {
p1 := Person{
"pprof.cn",
18,
}
fmt.Printf("%#v\n", p1) //main.Person{string:"pprof.cn", int:18}
fmt.Println(p1.string, p1.int) //pprof.cn 18
}
匿名字段默认采用类型名作为字段名,由于结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
1.9.2 同名问题
从上面的例子可以看出来,struct 不仅可以将struct作为匿名字段,自定义类型、内置类型都可以作为匿名字段,也可以进行相应的函数操作。
这里我们有个问题,Person上有一个name属性,如果Student上也有一个name属性,那么我们怎么办呢?其实在go里面,最外层的属性具有有限的访问权限,当你通过Student.name访问的时候是访问Student上的属性。同理,我们可以通过Student.Person访问Person上的属性,如:
type Human struct {
name stirng
age int
phone string
}
type Employee struct {
Human
phone string
}
func main(){
bob := Employee{Human{name: "Bob", age: 12, phone: "777444}, "3322"}
// 访问 Employee的phone属性
fmt.Println("bob phone is ", bob.phone) // bob phone is 3322
// 访问 Human的phone属性
fmt.Println("bob's person phone is ", bob.Human.phone)
}
1.10 自定义构造函数
1.11 结构体比较
如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用 == 或 != 运算符进行比较,但不支持 > 或 < 。
func main() {
s1 := Student{name:"Luffy", age:18 }
s2 := Student{name:"Luffy", age:18 }
fmt.Println("s1 == s2", s1 == s2) //s1 == s2 true
fmt.Println("s1 != s2", s1 != s2) //s1 != s2 false
}
2.JSON tag序列化
2.1 格式说明
常用于对struct结构体字段进行json序列化
2.tag里面加上omitempy,可以在序列化的时候忽略0值或者空值
package main
import (
"encoding/json"
"fmt"
)
// Product _
type Product struct {
Name string `json:"name"`
ProductID int64 `json:"product_id,omitempty"`
Number int `json:"number"`
Price float64 `json:"price"`
IsOnSale bool `json:"is_on_sale,omitempty"`
Amount int `json:"amount"`
}
func main() {
p := &Product{}
p.Name = "Xiao mi 6"
p.IsOnSale = false
p.Number = 10000
p.Price = 2499.00
p.ProductID = 0
data, _ := json.Marshal(p)
fmt.Println(string(data))
}
结果
{"name":"Xiao mi 6","number":10000,"price":2499,"amount":0}
// 值为false,0或者空字符串的ProductID和IsOnSalebool都没有出现在最后的json串里。
3.有些时候,我们在序列化或者反序列化的时候,可能结构体类型和需要的类型不一致,这个时候可以指定tag的type支持
package main
import (
"encoding/json"
"fmt"
)
// Product _
type Product struct {
Name string `json:"name"`
ProductID int64 `json:"product_id,string"`
Number int `json:"number,string"`
Price float64 `json:"price,string"`
IsOnSale bool `json:"is_on_sale,string"`
}
type ProductV2 struct {
Name string `json:"name"`
ProductID int64 `json:"product_id"`
Number int `json:"number"`
Price float64 `json:"price"`
IsOnSale bool `json:"is_on_sale"`
}
func main() {
var data = `{"name":"Xiao mi 6","product_id":"10","number":"10000","price":"2499","is_on_sale":"true"}`
p := &Product{}
err := json.Unmarshal([]byte(data), p)
fmt.Printf("[have type] err:%v,p:%+v\n", err, p)
p2 := &ProductV2{}
err = json.Unmarshal([]byte(data), p2)
fmt.Printf("[no type] err:%v,p:%+v\n", err, p2)
}
结果
[have type] err:<nil>,p:&{Name:Xiao mi 6 ProductID:10 Number:10000 Price:2499 IsOnSale:true}
[no type] err:json: cannot unmarshal string into Go struct field ProductV2.product_id of type int64,p:&{Name:Xiao mi 6 ProductID:0 Number:0 Price:0 IsOnSale:false}
其中json的type如下:
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for
若要在被嵌套结构体整体为空时使其在序列化结果中被忽略,不仅要在被嵌套结构体字段后加上json:“fileName,omitempty”,还要将其改为结构体指针。如:
package main
import (
"encoding/json"
"fmt"
)
type BodyInfo struct {
Weight float64
Height float64
}
type Student struct {
Name string `json:"name"`
Age int64
*BodyInfo `json:"bodyinfo,omitempty"` // 要使用指针
}
func main() {
s1 := Student{
Name: "jack",
Age: 20,
}
data, _ := json.Marshal(s1)
fmt.Println(string(data))
}
//结果
{"name":"jack","Age":20}
3.Form Tag
Gin中提供了模型绑定,将表单数据和模型进行绑定,方便参数校验和使用。
模型绑定:
// 表单数据
type LoginForm struct {
UserName string `form:"username"`
Password string `form:"password"`
Email string `form:"email"`
}
// model 或 service 层Model
type Email struct {
Email string
Password string
}
func EmailLogin (c *gin.Context) {
var email LoginForm
if err := c.ShouldBind(&email); err != nil {
...
}
// 获取表单数据局
args := Email {
Email: email.Email,
Password: email.Password,
}
// 对参数进行后续使用
...
}
通过 form:“email” 对表单email数据进行绑定。然后通过Bind()、ShouldBind()等方法获取参数值。
3.参考文章
传送门1
传送门2
传送门3
传送门4
JSON序列化部分
传送门5
传送门6