壹 JSON序列化介绍
JSON(JavaScript Object Notation)
是一种轻量级的数据交换格式,常用于前后端数据传输,易于人阅读和编写,同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""
包裹,使用冒号:
分隔,然后紧接着值;多个键值之间使用英文,
分隔。
为什么要序列化和反序列化?个人觉得是因为不同语言的数据类型的内存分配情况、语法等不同,我们需要一种统一的格式去识别和说明,不同语言之间数据的交换,这就需要序列化和反序列化,例如,Python
与Go
语言的不同,之间的交互我们就需要使用序列化和反序列化。
对于JSON序列化,我们会使用encoding/json
库或者第三方库github.com/tidwall/gjson
,进行JSON序列化和反序列化,这里我们简单介绍一下官方库encoding/json
库,而对于其他第三方库,自行了解学习,还可以通过网站json2go在线将json
格式转换为对应的go
结构体。
贰 JSON序列化和反序列化函数
对于JSON序列化,我们使用两个函数:Unmarshal
函数(JSON反序列化)和Marshal
函数(序列化)
2.1 Unmarshal函数——JSON格式的字符串转化为结构体
Unmarshal
函数是将JSON格式的字符串转化为结构体,这里需要注意 json.UnMarshal()
函数接收的参数是字节切片,所以相当于JSON格式的字节切片转化为结构体struct
。
格式:
func Unmarshal(data []byte, v interface{}) error
// data是要进行转化的json数据
// v是被转化后的struct数据,指针或者类型都行
举个例子:
//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{}
// Unmarshal函数是将JSON格式的字符串转化为结构体,这里需要注意json.UnMarshal() 函数接收的参数是字节切片,所以相当于JSON格式的字节切片转化为结构体
err = json.Unmarshal([]byte(str), c1)
if err != nil {
fmt.Println("json unmarshal failed!")
return
}
// 或者使用NewDecoder().Decode(),这里只是简单了解
// if err := json.NewDecoder([]byte(str)).Decode(&Class); err != nil {
// log.Fatalln(err)
// }
fmt.Printf("%#v\n", c1)
}
2.2 Marshal函数——将数据编码成JSON字符串
Marshal
函数是将Go语言的类型转化为JSON,这里需要注意 json.Marshal()
函数返回的是字节切片。
格式:
func Marshal(v interface{}) ([]byte, error)
// v是要进行转化的Go语言的类型数据
// []byte是被转化后的json字节切片数据
举个例子:
package main
import (
"encoding/json"
"fmt"
)
type Stu struct {
Name string `json:"name"`
Age int
HIgh bool
sex string
Class *Class `json:"class"`
}
type Class struct {
Name string
Grade int
}
func main() {
//实例化一个数据结构,用于生成json字符串
stu := Stu{
Name: "张三",
Age: 18,
HIgh: true,
sex: "男",
}
//指针变量
cla := new(Class)
cla.Name = "1班"
cla.Grade = 3
stu.Class = cla
//Marshal失败时err!=nil
jsonStu, err := json.Marshal(stu)
if err != nil {
fmt.Println("生成json字符串错误")
}
//jsonStu是[]byte类型,转化成string类型便于查看
fmt.Println(string(jsonStu)) // {"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}
}
从结果中可以看出:
- 只要是可导出成员(变量首字母大写),都可以转成JSON。因成员变量sex(变量首字母小写)是不可导出的,故无法转成JSON。
- 如果变量打上了JSON标签,如Name旁边的
json:"name"
,那么转化成的json key
就用该标签name
,否则取变量名作为key
,如Age
,HIgh
。 bool
类型也是可以直接转换为JSON的value
值。Channel
,complex
以及函数不能被编码JSON字符串。当然,循环的数据结构也不行,它会导致marshal
陷入死循环。- 如果是
指针变量
,编码时自动转换为它所指向的值
,如上面例子的cla
变量,当然,不传指针,Stu struct
的成员Class
如果换成Class struct
类型,效果也是一模一样的。只不过指针更快,且能节省内存空间。 - 最后,强调一句:·JSON编码成字符串后就是纯粹的字符串了。
上面的成员变量都是已知的类型,只能接收指定的类型,比如string
类型的Name
只能赋值string
类型的数据。但有时为了通用性,或使代码简洁,我们希望有一种类型可以接受各种类型的数据,并进行JSON
编码。这就用到了interface{}
类型。Go
的每一种类型都实现了该接口。因此,任何其他类型的数据都可以赋值给interface{}
类型。
package main
import (
"encoding/json"
"fmt"
)
type Stu struct {
Name interface{} `json:"name"`
Age interface{}
HIgh interface{}
sex interface{}
Class interface{} `json:"class"`
}
type Class struct {
Name string
Grade int
}
func main() {
//实例化一个数据结构,用于生成json字符串
stu := Stu{
Name: "张三",
Age: 18,
HIgh: true,
sex: "男",
}
//指针变量
cla := new(Class)
cla.Name = "1班"
cla.Grade = 3
stu.Class = cla
//Marshal失败时err!=nil
jsonStu, err := json.Marshal(stu)
if err != nil {
fmt.Println("生成json字符串错误")
}
//jsonStu是[]byte类型,转化成string类型便于查看
fmt.Println(string(jsonStu)) // {"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}
}
从结果中可以看出,无论是string
,int
,bool
,还是指针类型等,都可赋值给interface{}
类型,且正常编码,效果与前面的例子一样。
2.3 MarshalIndent函数——将JSON字符串格式化
我们都知道json
格式一般是下面这种格式:
{
"user_id": 1,
"user_name": "demo"
}
而在用Marshal
函数时,我们会发现输出的是一行json
字符串,并不美观,这时候我们就可以使用MarshalIndent
函数:
func MarshalIndent(v any, prefix, indent string) ([]byte, error)
# prefix表示json每一行都会输出的字符串
# indent表示在prefix基础上再输出
例子:
package main
import (
"encoding/json"
"fmt"
)
func main() {
type User struct {
UserId int `json:"user_id"`
UserName string `json:"user_name"`
}
// 输出json格式
u := User{UserId: 1, UserName: "demo"}
j, _ := json.MarshalIndent(u, "=", "++")
fmt.Println(string(j))
// 输出内容:
// {
// =++"user_id": 1,
// =++"user_name": "demo"
// =}
}
叁 JSON与Map、struct转化
例如我们在爬虫时,可能会获取到的时JSON
格式的数据,这时候我们就需要将其转化为Go语言常用的数据类型,才能对JSON
数据内容进行操作。
3.1 JSON与struct转化
JSON
转化为struct
有伍种:普通JSON
、JSON
内嵌普通JSON
、JSON
内嵌数组JSON
、JSON
内嵌具有动态Key
的JSON
、使用空接口。
思路就是创建一个与JSON
一样格式的struct
数据类型,使用Unmarshal
函数将JSON格式的字符串转化为结构体。
3.1.1 普通JSON
由于普通JSON结构比较简单,所以我们使用基本类型作为结构体的元素即可。
package main
import (
"encoding/json"
"fmt"
)
// Actress 女演员
// 使用基本类型作为结构体的元素
type Actress struct {
Name string
Birthday string
BirthPlace string
Opus []string
}
func main() {
// 普通JSON
jsonData := []byte(`{
"name":"迪丽热巴",
"birthday":"1992-06-03",
"birthPlace":"新疆乌鲁木齐市",
"opus":[
"《阿娜尔罕》",
"《逆光之恋》",
"《克拉恋人》"
]
}`)
var actress Actress
// 因为json.UnMarshal() 函数接收的参数是字节切片,所以需要把JSON字符串转换成字节切片。
err := json.Unmarshal(jsonData, &actress)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("姓名:%s\n", actress.Name)
fmt.Printf("生日:%s\n", actress.Birthday)
fmt.Printf("出生地:%s\n", actress.BirthPlace)
fmt.Println("作品:")
for _, val := range actress.Opus {
fmt.Println("\t", val)
}
}
3.1.2 JSON内嵌普通JSON
JSON内嵌普通JSON结构比较稍微比较复杂,这时候我们根据JSON数据可以看到JSON其中一个元素里面是JSON,这时候我们可以定义与JSON嵌套内的JSON一样的struct
数据类型,如下面例子中的Opus
,接着在定义与外围JSON数据一样的struct
数据类型,即下面例子的Actress
。
package main
import (
"encoding/json"
"fmt"
)
// Opus 作品
type Opus struct {
Date string
Title string
}
// Actress 女演员
type Actress struct {
Name string
Birthday string
BirthPlace string
// 可以看到Opus不是Go语言简单的数据类型,是一个Opus作品结构体类型
// 这样就与JSON的内容一致了
Opus Opus
}
func main() {
// JSON嵌套普通JSON
jsonData := []byte(`{
"name":"迪丽热巴",
"birthday":"1992-06-03",
"birthPlace":"新疆乌鲁木齐市",
"opus": {
"Date":"2013",
"Title":"《阿娜尔罕》"
}
}`)
var actress Actress
err := json.Unmarshal(jsonData, &actress)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("姓名:%s\n", actress.Name)
fmt.Printf("生日:%s\n", actress.Birthday)
fmt.Printf("出生地:%s\n", actress.BirthPlace)
fmt.Println("作品:")
fmt.Printf("\t%s:%s", actress.Opus.Date, actress.Opus.Title)
}
3.1.3 JSON内嵌数组JSON
JSON内嵌数组JSON,可以理解为结构体内嵌一个结构体切片,即JSON内嵌普通JSON中的内嵌结构体改为结构体切片。
package main
import (
"encoding/json"
"fmt"
)
type Opus struct {
Date string
Title string
}
type Actress struct {
Name string
Birthday string
BirthPlace string
Opus []Opus
}
func main() {
// JSON嵌套数组JSON
jsonData := []byte(`{
"name":"迪丽热巴",
"birthday":"1992-06-03",
"birthPlace":"新疆乌鲁木齐市",
"opus":[
{
"date":"2013",
"title":"《阿娜尔罕》"
},
{
"date":"2014",
"title":"《逆光之恋》"
},
{
"date":"2015",
"title":"《克拉恋人》"
}
]
}`)
var actress Actress
err := json.Unmarshal(jsonData, &actress)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("姓名:%s\n", actress.Name)
fmt.Printf("生日:%s\n", actress.Birthday)
fmt.Printf("出生地:%s\n", actress.BirthPlace)
fmt.Println("作品:")
for _, val := range actress.Opus {
fmt.Printf("\t%s - %s\n", val.Date, val.Title)
}
}
3.1.4 JSON内嵌具有动态Key的JSON
JSON内嵌具有动态Key的JSON,是由于JSON的Key不能确定,,这时候我们可以使用map
类型,来构造这个动态的Key。
package main
import (
"encoding/json"
"fmt"
)
// Opus 作品
type Opus struct {
Type string
Title string
}
// Actress 女演员
type Actress struct {
Name string
Birthday string
BirthPlace string
Opus map[string]Opus
}
func main() {
// 可以看到opus里面的2013、2014等Key是不确定的,但是Key类型都是string,这时候我们构造的Opus是map[string]Opus
jsonData := []byte(`{
"name":"迪丽热巴",
"birthday":"1992-06-03",
"birthPlace":"新疆乌鲁木齐市",
"opus":{
"2013":{
"Type":"近代革命剧",
"Title":"《阿娜尔罕》"
},
"2014":{
"Type":"奇幻剧",
"Title":"《逆光之恋》"
},
"2015":{
"Type":"爱情剧",
"Title":"《克拉恋人》"
}
}
}`)
var actress Actress
err := json.Unmarshal(jsonData, &actress)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("姓名:%s\n", actress.Name)
fmt.Printf("生日:%s\n", actress.Birthday)
fmt.Printf("出生地:%s\n", actress.BirthPlace)
fmt.Println("作品:")
for index, value := range actress.Opus {
fmt.Printf("\t日期:%s\n", index)
fmt.Printf("\t\t分类:%s\n", value.Type)
fmt.Printf("\t\t标题:%s\n", value.Title)
}
}
3.1.5 JSON内嵌数据不明确
当JSON内嵌不明确,我们可以考虑使用下面的map
,但是也可以使用空接口。
package main
import (
"encoding/json"
"fmt"
"reflect"
)
// Actress 女演员
type Actress struct {
Name string
Birthday string
BirthPlace string
Opus map[string]interface{}
}
func main() {
// 可以看到opus里面的2013的值是不确定的,这时候我们构造的Opus是map[string]interface{}
jsonData := []byte(`{
"name":"迪丽热巴",
"birthday":"1992-06-03",
"birthPlace":"新疆乌鲁木齐市",
"opus":{
"2013":1,
"2014":{
"Type":"奇幻剧",
"Title":"《逆光之恋》"
},
"2015":{
"Type":"爱情剧",
"Title":"《克拉恋人》"
}
}
}`)
var actress Actress
err := json.Unmarshal(jsonData, &actress)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("姓名:%s\n", actress.Name)
fmt.Printf("生日:%s\n", actress.Birthday)
fmt.Printf("出生地:%s\n", actress.BirthPlace)
fmt.Println("作品:")
for index, value := range actress.Opus {
fmt.Printf("\t日期:%s\n", index)
// 由于是空接口,我们需要使用到断言
Type := reflect.TypeOf(value).Kind()
// fmt.Println(Type)
// 使用switch对类型进行判断不同类型输出内容格式
switch Type {
case reflect.Float64:
// 断言获取对应内容
fmt.Printf("\t\t分类:%v\n", value.(float64))
fmt.Printf("\t\t标题:%v\n", value.(float64))
case reflect.Map:
fmt.Printf("\t\t分类:%v\n", value.(map[string]interface{})["Type"])
fmt.Printf("\t\t标题:%v\n", value.(map[string]interface{})["Title"])
}
}
}
3.2 JSON与map转化
当JSON内容格式过于复杂的时候,对应的结构体struct
会随之增加,这样会增加大量的代码。我们采用使用map[string]interface{}
加载JSON数据。就是使用json.Unmarshal
将 JSON 转化map[string]interface{}
。
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 定义make(map[string]interface{})
r := make(map[string]interface{})
result := `{"data":{"flag":"1","map":{"AOH":"上海虹桥","GGQ":"广州东","GZQ":"广州"},"result":["1","sas",1,"opopijh"]}}`
// 调用标准库encoding/json的Unmarshal
// 将JSON数据(JSON以字符串形式表示)转换成[]byte,并将数据加载到对象r的内存地址
json.Unmarshal([]byte(result), &r)
// r["data"]是读取JSON最外层的key,即data
// 如果嵌套JSON数据,则使用map[string]interface{}读取下一层的JSON数据
// 如读取key为data里面嵌套的result,即:r["data"].(map[string]interface{})["result"]
// 如果JSON的某个key的数据以数组表示,则使用([]interface{})[index]读取数组中某个数据。
// 如读取key为result的第四个数据:r["data"].(map[string]interface{})["result"].([]interface{})[3]
fmt.Println(r["data"].(map[string]interface{})["result"].([]interface{})[3])
}
- 这里的逻辑就是
r["data"]
获取的是{"flag":"1","map":{"AOH":"上海虹桥","GGQ":"广州东","GZQ":"广州"},"result":["1","sas",1,"opopijh"]}
- 而
r["data"].(map[string]interface{})["result"]
中(map[string]interface{})["result"]
意思是下一个要获取的元素是(map[string]interface{})
,获取的值是["1","sas",1,"opopijh"]
- 接着
r["data"].(map[string]interface{})["result"].([]interface{})[3]
意思是下一个要获取的元素类型是([]interface{})
,获取的值是opopijh
。
3.3 选择struct还是map?
对于JSON转化,是选择struct
还是map
?个人觉得根据情况决定,如果数据量不大JSON嵌套不复杂或者是JSON的逻辑清晰时,可以使用struct
,而JSON嵌套复杂,逻辑不确定,可以考虑使用map
,但是会出现一个问题,使用反射,反射会拉低程序执行效率。个人而言,更倾向于使用struct
,因为struct
可以配套空接口,对不确定的类型进行匹配。
更多关于JSON的介绍可以看这两篇文章: