结构体与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)
}

go学习-结构体与JSON序列化_序列化

结构体标签(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:"沙河娜扎"}
}

go学习-结构体与JSON序列化_json_02

忽略某个字段

如果你想在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)
}

go学习-结构体与JSON序列化_字段_03

添加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)
}

go学习-结构体与JSON序列化_json_04

结构体和方法补充知识点

因为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
go学习-结构体与JSON序列化_字段_05
正确的做法是在方法中使用传入的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(另一个空间,不共用一个地址)
}

go学习-结构体与JSON序列化_字段_06