• go 序列化与反序列化


    go 序列化与反序列化

    1 json 序列化和反序列化

    Go语言对json的解析函数在encoding/json包里面,主要是编码和解码两个函数。

    1.1 序列化Marshal函数

    func Marshal(v interface{}) ([]byte, error)
    
    • 1

    Marshal函数返回v的json编码

    注意

    布尔类型编码为json布尔类型。

    浮点数、整数和Number类型的值编码为json数字类型。

    字符串编码为json字符串。

    数组和切片类型的值编码为json数组,但[]byte编码为base64编码字符串,nil切片编码为null

    结构体的值编码为json对象。每一个导出字段变成该对象的一个成员,除非以下两种情况:

    字段的标签是"-"
    字段是空值,而其标签指定了omitempty选项
    
    • 1
    • 2

    空值是false、0、””、nil指针、nil接口、长度为0的数组、切片、映射。对象默认键字符串是结构体的字段名,但可以在结构体字段的标签里指定。结构体标签值里的”json”键为键名,后跟可选的逗号和选项,举例如下:

    Age int `json:"-"` // 字段被本包忽略
    Name string `json:"myName"` // 字段在json里的键为"myName"
    Sex int `json:"mySex,omitempty"` // 字段在json里的键为"myName"且如果字段为空值将在对象中省略掉
    Hobby int `json:",omitempty"`// 字段在json里的键为"Hobby"(默认值),但如果字段为空值会跳过;注意前导的逗号
    
    • 1
    • 2
    • 3
    • 4

    “string”选项标记一个字段在编码json时应编码为字符串。它只适用于字符串、浮点数、整数类型的字段

    Int64String int64 `json:",string"`
    
    • 1

    如果键名是只含有unicode字符、数字、美元符号、百分号、连字符、下划线和斜杠的非空字符串,将使用它代替字段名。

    匿名的结构体字段一般序列化为他们内部的导出字段就好像位于外层结构体中一样。如果一个匿名结构体字段的标签给其提供了键名,则会使用键名代替字段名,而不视为匿名。

    Go结构体字段的可视性规则用于供json决定那个字段应该序列化或反序列化时是经过修正了的。如果同一层次有多个(匿名)字段且该层次是最小嵌套的(嵌套层次则使用默认go规则),会应用如下额外规则:

    1)json标签为”-“的匿名字段强行忽略,不作考虑;

    2)json标签提供了键名的匿名字段,视为非匿名字段;

    3)其余字段中如果只有一个匿名字段,则使用该字段;

    4)其余字段中如果有多个匿名字段,但压平后不会出现冲突,所有匿名字段压平;

    5)其余字段中如果有多个匿名字段,但压平后出现冲突,全部忽略,不产生错误。

    对匿名结构体字段的管理是从go1.1开始的,在之前的版本,匿名字段会直接忽略掉。

    Map类型的值编码为json对象。Map的键必须是字符串,对象的键直接使用映射的键。

    指针类型的值编码为其指向的值(的json编码)。nil指针编码为null。

    接口类型的值编码为接口内保持的具体类型的值(的json编码)。nil接口编码为null。

    通道、复数、函数类型的值不能编码进json。会导致Marshal函数返回UnsupportedTypeError错误

    1.2 反序列化Unmarshal函数

    func Unmarshal(data []byte, v interface{}) error
    
    • 1

    Unmarshal函数解析json编码的数据并将结果存入v指向的值。

    Unmarshal和Marshal做相反的操作,必要时申请map、切片或指针,遵循如下规则:

    要将json数据解码写入一个指针,Unmarshal函数首先处理json数据是json字面值null的情况。此时,函数将指针设为nil;否则,函数将json数据解码写入指针指向的值;如果指针本身是nil,函数会先申请一个值并使指针指向它。

    要将json数据解码写入一个结构体,函数会匹配输入对象的键和Marshal使用的键(结构体字段名或者它的标签指定的键名),优先选择精确的匹配,但也接受大小写不敏感的匹配。

    要将json数据解码写入一个接口类型值,函数会将数据解码为如下类型写入接口:

    Bool                   对应JSON布尔类型
    float64                对应JSON数字类型
    string                 对应JSON字符串类型
    []interface{}          对应JSON数组
    map[string]interface{} 对应JSON对象
    nil                    对应JSON的null
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果一个JSON值不匹配给出的目标类型,或者如果一个json数字写入目标类型时溢出,Unmarshal函数会跳过该字段并尽量完成其余的解码操作。如果没有出现更加严重的错误,本函数会返回一个描述第一个此类错误的详细信息的UnmarshalTypeError。

    JSON的null值解码为go的接口、指针、切片时会将它们设为nil,因为null在json里一般表示“不存在”。解码json的null值到其他go类型时,不会造成任何改变,也不会产生错误。

    当解码字符串时,不合法的utf-8或utf-16代理(字符)对不视为错误,而是将非法字符替换为unicode字符U+FFFD。

    1.3 示例

    序列化结构体

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    //定义一个简单的结构体 Person
    type Person struct {
    	Name     string
    	Age      int
    	Birthday string
    	Sex      float32
    	Hobby    string
    }
    
    //写一个 testStruct()结构体的序列化方法
    func testStruct() {
    	person := Person{
    		Name:     "小崽子",
    		Age:      50,
    		Birthday: "2019-09-27",
    		Sex:      1000.01,
    		Hobby:    "泡妞",
    	}
    
    	// 将Monster结构体序列化
    	data, err := json.Marshal(&person)
    	if err != nil {
    		fmt.Printf("序列化错误 err is %v", err)
    	}
    	//输出序列化结果
    	fmt.Printf("person序列化后 = %v", string(data))
        //反序列化
    	person2 := Person{}
    	json.Unmarshal(data,&person2)
    	fmt.Println(person2)
    }
    func main()  {
    	testStruct()
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    序列化map

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    func testMap() {
    	//定义一个map
    	var a map[string]interface{}
    	//使用map之前 必须make一下
    	a = make(map[string]interface{})
    	a["name"] = "小崽子"
    	a["age"] = 8
    	a["address"] = "上海市浦东新区"
    
    	// 将a map结构体序列化
    	data, err := json.Marshal(a)
    	if err != nil {
    		fmt.Printf("序列化错误 err is %v", err)
    	}
    	//输出序列化结果
    	fmt.Printf("map序列化后 = %v", string(data))
        //反序列化
    	var a1 map[string]interface{}
    	json.Unmarshal(data,&a1)
    	fmt.Println(a1)
    }
    func main() {
    	testMap()
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    序列化slice

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    // slice进行序列化
    func testSlice() {
    	var slice []map[string]interface{} // 定义了一个切片,里面是map格式 map[string]interface{}
    	var m1 map[string]interface{}      //定义切片中的第一个map M1
    	m1 = make(map[string]interface{})
    	m1["name"] = "小崽子"
    	m1["age"] = 16
    	m1["address"] = [2]string{"上海市", "浦东新区"}
    	slice = append(slice, m1)
    
    	var m2 map[string]interface{} //定义切片中的第2个map M2
    	m2 = make(map[string]interface{})
    	m2["name"] = "大崽子"
    	m2["age"] = 36
    	m2["address"] = "北京市"
    	slice = append(slice, m2)
    
    	// 将slice进行序列化
    	data, err := json.Marshal(slice)
    	if err != nil {
    		fmt.Printf("序列化错误 err is %v", err)
    	}
    	//输出序列化结果
    	fmt.Printf("slice序列化后 = %v", string(data))
        //反序列化结果
    	var slice2 []map[string]interface{}
    	json.Unmarshal(data,&slice2)
    	fmt.Println(slice2)
    }
    func main() {
    	testSlice()
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    2 xml 解析

    2.1 案例一

    device.xml

    
    <devices version="3">
        <host id="1">
            <hostName>订单服务器hostName>
            <hostCode>10001hostCode>
            <hostDate>2023-03-01hostDate>
        host>
        <host id="2">
            <hostName>数据库服务器hostName>
            <hostCode>100002hostCode>
            <hostDate>2024-03-15hostDate>
        host>
        <host id="3">
            <hostName>Redis服务器hostName>
            <hostCode>100003hostCode>
            <hostDate>2024-05-13hostDate>
        host>
    devices>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.2 解析xml

    type Device struct {
    	XMLName xml.Name `xml:"devices"`
    	Version string   `xml:"version,attr"`
    	Host    []Host   `xml:"host"`
    	Desc    string   `xml:",innerxml"`  // 反序列化时使用,xml格式原样输出
    }
    
    type Host struct {
    	XMLName  xml.Name `xml:"host"`
    	HostName string   `xml:"hostName"`
    	HostCode string   `xml:"hostCode"`
    	HostDate string   `xml:"hostDate"`
    	ID       int      `xml:"id,attr"`
    }
    
    func main() {
    	var d Device
    	data, _ := ioutil.ReadFile("./device.xml")
    	xml.Unmarshal(data, &d)
    	fmt.Println(d)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.2 生成xml

    type Device struct {
    	XMLName xml.Name `xml:"devices"`
    	Version string   `xml:"version,attr"`
    	Host    []Host   `xml:"host"`
    	Desc    string   `xml:",innerxml"`
    	Comment    string   `xml:",comment"` // 序列化的时候用,生成注释
    	Data    string   `xml:",chardata"` // 序列化用,和innerxml相反,
    
    }
    type Host struct {
    	XMLName  xml.Name `xml:"host"`
    	HostName string   `xml:"hostName"`
    	HostCode string   `xml:"hostCode"`
    	HostDate string   `xml:"hostDate"`
    	ID       int      `xml:"id,attr"`
    }
    
    
    func main() {
    	var d Device=Device{Comment: "这是注释",Data:"测试"}
    	d.Host = append(d.Host,Host{HostName: "订单服务",HostCode: "1001",HostDate: "2024-09-08",ID: 1} )
    	d.Host = append(d.Host,Host{HostName: "数据服务",HostCode: "1002",HostDate: "2024-09-08"} )
    	d.Host = append(d.Host,Host{HostName: "商品服务",HostCode: "1003",HostDate: "2024-09-08"} )
    	data,err:=xml.Marshal(d)
    	if err!=nil {
    		fmt.Println("出错:",err)
    		return
    	}
    	fmt.Println(string(data))
    	// 1 不带xml头的
    	//ioutil.WriteFile("./device2.xml",data,0666)
    	// 2 带xml头的
    	headByte:=[]byte(xml.Header) // 把xml头转成byte切片
    	headByte=append(headByte,data...) // 把数据拼到后面
    	ioutil.WriteFile("./device2.xml",headByte,0666)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.3 tag解释

    "-",字段不会输出
    "name,attr",以name作为属性名,字段值作为输出这个XML元素的属性
    ",attr",以这个结构体struct的字段名作为属性名输出XML元素的属性,name默认是字段名
    ",innerxml",原样输出,不进行常规的编码过程
    
    ",comment",作为XML注释来输出,不进行编码过程,字段中不能有“--”字符串
    ",omitempty",若字段值为空、那么字段不会被输出到XML,空值有:false0nil指针,nil接口,任意长度为0的Slice、数组结构、Map结构或者string
    ",chardata",输出XML元素为character data而非element。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3 MessagePack格式

    先看官方的定义:MessagePack是一种高效的二进制序列化格式。它允许您像JSON一样在多个语言之间交换数据。但是,它更快并且更小。

    从官方定义中,可以有如下的结论:

    1. MessagePack是一个二进制序列化格式,因而它序列化的结果可以在多个语言间进行数据的交换。
    2. 从性能上讲,它要比json的序列化格式要好。
    3. 从结果大小上讲,它要比json的序列化结果要小。
    • 需要安装第三方包:go get github.com/vmihailenco/msgpack

    go 的第三方库需要通过 go get 地址的方式下载,第三方模块基本都在github上,国内可以通过配置代理的方式来加速下载

    1. 命令方式全局修改: 
    	go env -w GOPROXY=https://goproxy.cn,direct
    2. 只改当前项目
    	在goland 中修改go modules 配置,局部修改当前项目的go env
    
    • 1
    • 2
    • 3
    • 4
    package main
    
    import (
    	"fmt"
    	"github.com/vmihailenco/msgpack"
    )
    
    type Dog struct {
    	Name string
    	Age int
    	Type string
    }
    
    func main() {
    	var dogs []Dog
    	dog:=Dog{"小奶狗",1,"田园犬"}
    	dog2:=Dog{"小陆",1,"贵宾"}
    	dog3:=Dog{"小杨",1,"二哈"}
    	dogs=append(dogs,dog,dog2,dog3)
    	//fmt.Println(dogs)
    	// 序列化
    	res,_:=msgpack.Marshal(&dogs)
    	fmt.Println(string(res))
    
    	// 反序列化
    	var dogs1 []Dog
    	msgpack.Unmarshal(res,&dogs1)
    	fmt.Println(dogs1)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    4 gob格式

    标准库gob是golang提供的“私有”的编解码方式,它的效率会比json,xml等更高,特别适合在Go语言程序间传递数据

    package main
    
    import (
    	"bytes"
    	"encoding/gob"
    	"fmt"
    	"io/ioutil"
    )
    
    type Fish struct {
    	Name string
    	Age int
    }
    
    func main() {
    	// 编码
    	//var fishes=[]Fish{{"金鱼",1},{"鲸鱼",100}}
    	//var buf =&bytes.Buffer{}
    	var buf =new(bytes.Buffer)
    	//encoder:=gob.NewEncoder(buf)
    	//encoder.Encode(fishes)
    	//fmt.Println(buf)
    	 保存到文件中,buf格式转成字节切片
    	//ioutil.WriteFile("fish.gob",buf.Bytes(),0666)
    
    	// 解码
    	var fishes []Fish
    	file,_ := os.Open("./fish.gob")
    	decoder:=gob.NewDecoder(file)
    	decoder.Decode(&fishes)
    	fmt.Println(fishes)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    C++ 【list,priority_queue模拟实现】
    「互动有礼,感谢有你」参与互动就有机会获赠 Navicat Premium 16
    USB通讯原理
    看动画,学Java基础教程13:变量
    应用计量经济学问题~
    在 Python 中打印二叉树
    9、C++高级语法
    阿里云ubuntu服务器搭建ftp服务器
    【译】为你的 ASP. NET Core Web API 创建 Microsoft Power App
    用队列实现栈(C语言版本)
  • 原文地址:https://blog.csdn.net/qq_55752792/article/details/126103207