LOADING

正在加载

Go基础学习(柒)——JSON序列化

壹 JSON序列化介绍

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,常用于前后端数据传输,易于人阅读和编写,同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。

为什么要序列化和反序列化?个人觉得是因为不同语言的数据类型的内存分配情况、语法等不同,我们需要一种统一的格式去识别和说明,不同语言之间数据的交换,这就需要序列化和反序列化,例如,PythonGo语言的不同,之间的交互我们就需要使用序列化和反序列化。

对于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,如AgeHIgh
  • bool类型也是可以直接转换为JSON的value值。Channelcomplex 以及函数不能被编码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}}
}

从结果中可以看出,无论是stringintbool,还是指针类型等,都可赋值给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有伍种:普通JSONJSON内嵌普通JSONJSON内嵌数组JSONJSON内嵌具有动态KeyJSON、使用空接口。

思路就是创建一个与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的介绍可以看这两篇文章:

avatar
小C&天天

修学储能 先博后渊


今日诗句