默认的序列化行为

在线运行open in new window启动 AI 助手open in new window

type person struct {
	Name    string
	Age     int
	Salary  int
	address string // 没有导出
}

func main() {
	p1 := person{"zhangsan", 20, 8000, "street 99"}
	b, err := json.Marshal(p1)
	fmt.Println(err == nil) // true
	fmt.Println(string(b))  // {"Name":"zhangsan","Age":20,"Salary":8000}
}

代码演示了如何使用 Go 的标准库 encoding/json 将一个结构体对象编码成 JSON 格式的字符串。结构体类型 person 定义了四个字段,其中 address 字段小写字母开头,表示是私有的,不会被编码到 JSON 字符串中。主函数中定义了一个 person 类型的对象 p1,然后使用 json.Marshal 函数将其编码成 JSON 字符串,并打印出来。如果没有错误,则输出的第一行是 true,表示编码成功。第二行输出的是 JSON 格式的字符串 {"Name":"zhangsan","Age":20,"Salary":8000}。

结构体的序列化和反序列化

在线运行open in new window启动 AI 助手open in new window

type person struct {
	Name    string // Name
	Age     int    // Age
	Salary  int    `json:"pay"` // pay
	address string // 没有导出
}
 
func main() {
	p1 := person{"zhangsan", 20, 8000, "street 99"}
	b, _ := json.Marshal(p1)
	fmt.Println(string(b)) // {"Name":"zhangsan","Age":20,"pay":8000}

	p2 := person{}
	_ = json.Unmarshal([]byte(`{"Name":"lisi","Age":21,"pay":9000,"address":"street 100"}`), &p2)
	fmt.Printf("%+v", p2) // {Name:lisi Age:21 Salary:9000 address:}
}

代码定义了一个结构体 person,其中有四个字段 Name、Age、Salary 和 address,其中 Salary 字段使用了 json:"pay" 的标签,表示在 JSON 序列化和反序列化时,使用 pay 作为字段名。

在 main 函数中,首先创建了一个 person 类型的变量 p1,使用 JSON 序列化将其转换成了 JSON 字符串并输出到控制台。由于 Salary 字段使用了 pay 标签,因此输出的 JSON 字符串中对应字段名也是 pay。

接着创建了一个空的 person 类型的变量 p2,使用 JSON 反序列化将一个 JSON 字符串转换成 p2 对应的结构体,并将其输出到控制台。由于 address 字段没有导出(即字段名首字母小写),在 JSON 反序列化时会被忽略,因此输出的结构体中 address 字段的值为空字符串。

禁止输出和忽略零值字段

在线运行open in new window启动 AI 助手open in new window

type person struct {
	Name   string `json:"name"`
	Age    int    `json:"age,omitempty"` // 零值(zero value)不会输出
	Salary int    `json:"-"`             // 有无值均不会序列化
}

func main() {
	p1 := person{"zhangsan", 0, 8000}
	b, _ := json.Marshal(p1)
	fmt.Println(string(b)) // {"name":"zhangsan"}

	p2 := person{}
	_ = json.Unmarshal([]byte(`{"name":"lisi","age":21,"salary":9000}`), &p2)
	fmt.Printf("%+v", p2) // {Name:lisi Age:21 Salary:0}
}

程序定义了一个名为 person 的结构体,其中 Name 字段的 json 标记被设置为 name,Age 字段的 json 标记被设置为 age 并具有 omitempty 选项,Salary 字段的 json 标记被设置为 -。在 main 函数中,创建了一个 person 类型的变量 p1,其中 Name 为 "zhangsan",Age 为 0,Salary 为 8000。然后使用 json.Marshal() 将其序列化为 JSON 字符串,并输出到标准输出流。由于 Age 字段的值为零值,因此不会将其序列化到 JSON 中。Salary 字段的 json 标记被设置为 -,表示无论该字段是否有值,都不会将其序列化到 JSON 中。因此,最终输出的 JSON 字符串中只包含 Name 字段,即 {"name":"zhangsan"}。接下来,创建了另一个 person 类型的变量 p2,并使用 json.Unmarshal() 将一个 JSON 字符串解析为该结构体变量。由于该 JSON 字符串中只包含 name 和 age 字段,因此只有 Name 和 Age 字段被解析到 p2 中,而 Salary 字段被忽略。由于 Age 字段在 JSON 字符串中具有非零值,因此被解析为 21,并存储在 p2 中的 Age 字段中。最后,使用 %+v 格式化字符串输出 p2 的值,以显示结构体的所有字段及其值。

键值对的序列化和反序列化

在线运行open in new window启动 AI 助手open in new window

func main() {
	m1 := map[string]interface{}{
		"one": 1,
		"extra": map[int]string{
			2: "two",
		},
	}

	js, _ := json.Marshal(m1)
	fmt.Println(string(js))
	// {"extra":{"2":"two"},"one":1}

	var m2 map[string]interface{}
	m2str := `{"one":1, "extra":{"2":"two"}}`
	json.Unmarshal([]byte(m2str), &m2)
	fmt.Println(m2)
	// map[extra:map[2:two] one:1]
}

在第一个示例中,我们定义了一个类型为 map[string]interface{} 的变量 m1。m1 包含两个键值对:键为字符串类型 "one",对应的值为整数类型 1;键为字符串类型 "extra",对应的值为嵌套的 map[int]string 类型。

我们使用 json.Marshal() 函数将 m1 转换为 JSON 字符串,并打印结果。json.Marshal() 函数将 Go 类型转换为 JSON 字符串。在转换期间,map 类型将转换为 JSON 对象,其中每个键值对将成为 JSON 对象的属性。

在第二个示例中,我们定义了一个名为 m2 的变量,并使用 json.Unmarshal() 函数将一个 JSON 字符串解析到 m2 中。json.Unmarshal() 函数将 JSON 字符串解析为 Go 类型。在这种情况下,JSON 对象将转换为 map[string]interface{} 类型。嵌套的 JSON 对象将被转换为嵌套的 map[string]interface{} 类型。在解析期间,JSON 对象中的每个属性将成为 map 的键值对。

对输出的 json 格式化

在线运行open in new window启动 AI 助手open in new window

type person struct {
	Name    string
	Age     int
	Salary  int
	address string // 没有导出
}

func main() {
	p1 := person{"zhangsan", 20, 8000, "street 99"}

	b1, _ := json.MarshalIndent(p1, "", "\t")
	fmt.Println(string(b1))

	b2, _ := json.MarshalIndent(p1, "", "  ")
	fmt.Println(string(b2))

	b3, _ := json.MarshalIndent(p1, "@", "--")
	fmt.Println(string(b3))
}

在 json.MarshalIndent 函数中,第一个参数为要编码的数据,第二个参数为缩进前缀,第三个参数为缩进字符。缩进字符通常是一个空格或者制表符,缩进前缀用于控制缩进的层次。可以看到,通过调整缩进前缀和缩进字符,我们可以更好地控制输出格式。

使用 RawMessage 延迟解析

在线运行open in new window启动 AI 助手open in new window

type Circle struct {
	Radius int `json:"radius"`
}

type Rectangle struct {
	Width  int `json:"width"`
	Height int `json:"height"`
}

type Good struct {
	Category string          `json:"category"`
	Price    int             `json:"price"`
	Shape    json.RawMessage `json:"shape"` // 未知类型
}

func restore(data []byte) {
	var g Good
	_ = json.Unmarshal(data, &g)

	fmt.Printf("good: %v\n", g)

	switch g.Category {
	case "circle":
		c := Circle{}
		_ = json.Unmarshal(g.Shape, &c)
		fmt.Printf("circle: %#+v\n", c)
	case "rectangle":
		r := Rectangle{}
		_ = json.Unmarshal(g.Shape, &r)
		fmt.Printf("rectangle: %#+v\n", r)
	}
}

func main() {
	g1 := `{"category":"circle","price":"10","shape":{"radius":10}}`
	g2 := `{"category":"rectangle","price":"10","shape":{"width":10,"height":5}}`

	restore([]byte(g1))
	// good: {circle 0 [123 34 114 97 100 105 117 115 34 58 49 48 125]}
	// circle: main.Circle{Radius:10}

	restore([]byte(g2))
	// good: {rectangle 0 [123 34 119 105 100 116 104 34 58 49 48 44 34 104 101 105 103 104 116 34 58 53 125]}
	// rectangle: main.Rectangle{Width:10, Height:5}
}

json.RawMessage类型表示一个未解码的 JSON 数据,可以用于恢复一个未知类型的 JSON 对象。

在这个示例中,我们定义了三个类型:Circle,Rectangle 和 Good。Good 类型中包含一个字段 Shape,它的类型为 json.RawMessage,可以存储未知类型的 JSON 对象。然后我们在 restore 函数中将 Good 对象中的 Shape 解码为一个未知类型的值。根据 Good 对象的 Category 字段的值,我们将这个未知类型的值解码为 Circle 或 Rectangle 对象,最后输出解码后的值。

在 main 函数中,我们定义了两个 JSON 对象 g1 和 g2,分别代表了 Circle 和 Rectangle 类型的 Good 对象。然后我们调用 restore 函数对这两个 JSON 对象进行解码并输出解码后的值。

Last Updated:
Contributors: Bob Wang