结构体与JSON序列化
JSON数据与结构体之间相互转换
基本的序列化:json.Marshal()(序列化:结构体-->JSON格式的字符串)与json.Unmarshal(反序列化:JSON格式的字符串-->结构体)
package main
import ("fmt"
"encoding/json"
)
//Student 学生
type Student struct {
ID int
Gender string
Name string
}
//Class 班级
type Class struct {
Title string
Students []*Student
}
func main() {
c := &Class{
Title: "101",
Students: make([]*Student, 0, 200),
}
for i := 0; i < 10; i++ {
stu := &Student{
Name: fmt.Sprintf("stu%02d", i),
Gender: "男",
ID: i,
}
c.Students = append(c.Students, stu)
}
//JSON序列化:结构体-->JSON格式的字符串
data, err := json.Marshal(c)
if err != nil {
fmt.Println("json marshal failed")
return
}
fmt.Printf("json序列化结果:%s\n", data)
//JSON反序列化:JSON格式的字符串-->结构体
str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
c1 := &Class{}
err = json.Unmarshal([]byte(str), c1)
if err != nil {
fmt.Println("json unmarshal failed!")
return
}
fmt.Printf("json反序列化结果:%#v\n", c1)
}
结构体标签(Tag)
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag在结构体字段的后方定义,由一对反引号包裹起来,
具体的格式如下:key1:"value1" key2:"value2"
注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
使用json tag指定字段名
序列化与反序列化默认情况下使用结构体的字段名,我们可以通过给结构体字段添加tag来指定json序列化生成的字段名。
package main
import ("fmt"
"encoding/json"
)
// 使用json tag指定序列化与反序列化时的行为
//Student 学生
type Student struct {
ID int `json:"id"` //通过指定tag实现json序列化该字段时的key,指定json序列化/反序列化时使用小写id
Gender string //json序列化是默认使用字段名作为key
name string //私有不能被json包访问
}
func main() {
s1 := Student{
ID: 1,
Gender: "男",
name: "沙河娜扎",
}
data, err := json.Marshal(s1)
if err != nil {
fmt.Println("json marshal failed!")
return
}
fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"}
fmt.Printf("%#v\n", s1) //main.Student{ID:1, Gender:"男", name:"沙河娜扎"}
}
忽略某个字段
如果你想在json序列化/反序列化的时候忽略掉结构体中的某个字段,可以按如下方式在tag中添加-。
// 使用json tag指定json序列化与反序列化时的行为
type Person struct {
Name string `json:"name"` // 指定json序列化/反序列化时使用小写name
Age int64
Weight float64 `json:"-"` // 指定json序列化/反序列化时忽略此字段
}
忽略空值字段
当 struct 中的字段没有值时, json.Marshal() 序列化的时候不会忽略这些字段,而是默认输出字段的类型零值(例如int和float类型零值是 0,string类型零值是"",对象类型零值是 nil)。
如果想要在序列序列化时忽略这些没有值的字段时,可以在对应字段添加omitempty tag。
例如:没有添加omitempty
package main
import ("fmt"
"encoding/json"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Hobby []string `json:"hobby"`
}
func main() {
u1 := User{
Name: "七米",
}
// struct -> json string
b, err := json.Marshal(u1)
if err != nil {
fmt.Printf("json.Marshal failed, err:%v\n", err)
return
}
fmt.Printf("str:%s\n", b)
}
添加omitempty:序列化结果中没有email和hobby字段
package main
import ("fmt"
"encoding/json"
)
type User struct {
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
}
func main() {
u1 := User{
Name: "七米",
}
// struct -> json string
b, err := json.Marshal(u1)
if err != nil {
fmt.Printf("json.Marshal failed, err:%v\n", err)
return
}
fmt.Printf("str:%s\n", b)
}
结构体和方法补充知识点
因为slice和map这两种数据类型都包含了指向底层数据的指针,因此我们在需要复制它们时要特别注意。
package main
import ("fmt"
)
type Person struct {
name string
age int8
dreams []string
}
func (p *Person) SetDreams(dreams []string) {
p.dreams = dreams
}
func main() {
p1 := Person{name: "小王子", age: 18}
p2 := Person{name: "小王子2", age: 20}
data := []string{"吃饭", "睡觉", "打豆豆"}
p1.SetDreams(data)
// 你真的想要修改 p1.dreams 吗?
data[1] = "不睡觉"
p2.SetDreams(data)
fmt.Println(p1.dreams) // 不想改变
fmt.Println(p2.dreams) // 想改变
}
结果:p1的内容也改变了。而我们真正想改变的只有p2的内容,不想改变p1
正确的做法是在方法中使用传入的slice的拷贝进行结构体赋值。
这样就会只改变副本,而不会全部改变
原因:由于切片和map是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。
Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,改变一个值,另一个的值就不会变化了。因为地址不同了。
func (p *Person) SetDreams(dreams []string) {
p.dreams = make([]string, len(dreams))//动态创建长度为dreams的切片
copy(p.dreams, dreams)//将dreams拷贝到p.dreams(另一个空间,不共用一个地址)
}