9-常用标准库之-序列化和反序列化
一 json 序列化和反序列化
Go语言对json的解析函数在encoding/json包里面,主要是编码和解码两个函数。
1.1 序列化Marshal函数
func Marshal(v interface{}) ([]byte, error)
Marshal函数返回v的json编码
注意:
布尔类型编码为json布尔类型。
浮点数、整数和Number类型的值编码为json数字类型。
字符串编码为json字符串。
数组和切片类型的值编码为json数组,但[]byte编码为base64编码字符串,nil切片编码为null
结构体的值编码为json对象。每一个导出字段变成该对象的一个成员,除非以下两种情况:
字段的标签是"-"
字段是空值,而其标签指定了omitempty选项
空值是false、0、””、nil指针、nil接口、长度为0的数组、切片、映射。对象默认键字符串是结构体的字段名,但可以在结构体字段的标签里指定。结构体标签值里的”json”键为键名,后跟可选的逗号和选项,举例如下:
Age int `json:"-"` // 字段被本包忽略
Name string `json:"myName"` // 字段在json里的键为"myName"
Sex int `json:"myName,omitempty"` // 字段在json里的键为"myName"且如果字段为空值将在对象中省略掉
Hobby int `json:",omitempty"`// 字段在json里的键为"Hobby"(默认值),但如果字段为空值会跳过;注意前导的逗号
“string”选项标记一个字段在编码json时应编码为字符串。它只适用于字符串、浮点数、整数类型的字段
Int64String int64 `json:",string"`
如果键名是只含有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
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
如果一个JSON值不匹配给出的目标类型,或者如果一个json数字写入目标类型时溢出,Unmarshal函数会跳过该字段并尽量完成其余的解码操作。如果没有出现更加严重的错误,本函数会返回一个描述第一个此类错误的详细信息的UnmarshalTypeError。
JSON的null值解码为go的接口、指针、切片时会将它们设为nil,因为null在json里一般表示“不存在”。解码json的null值到其他go类型时,不会造成任何改变,也不会产生错误。
当解码字符串时,不合法的utf-8或utf-16代理(字符)对不视为错误,而是将非法字符替换为unicode字符U+FFFD。
1.3 示例
Golang - 序列化结构体
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()
}
Golang - 序列化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()
}
Golang - 序列化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()
}
二 xml 解析
2.1 案例一
device.xml
<?xml version="1.0" encoding="utf-8"?>
<devices version="3">
<host id="1">
<hostName>订单服务器</hostName>
<hostCode>10001</hostCode>
<hostDate>2023-03-01</hostDate>
</host>
<host id="2">
<hostName>数据库服务器</hostName>
<hostCode>100002</hostCode>
<hostDate>2024-03-15</hostDate>
</host>
<host id="3">
<hostName>Redis服务器</hostName>
<hostCode>100003</hostCode>
<hostDate>2024-05-13</hostDate>
</host>
</devices>
2.2 解析xml
type Device struct {
XMLName xml.Name `xml:"devices"`
Version string `xml:"version,attr"`
Host []Host `xml:"host"`
Desc string `xml:",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
data, _ := ioutil.ReadFile("./device.xml")
xml.Unmarshal(data, &d)
fmt.Println(d)
}
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:"<host><Name>测试</Name></host>"}
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)
}
2.3 tag解释
"-",字段不会输出
"name,attr",以name作为属性名,字段值作为输出这个XML元素的属性
",attr",以这个结构体struct的字段名作为属性名输出XML元素的属性,name默认是字段名
",innerxml",原样输出,不进行常规的编码过程
",comment",作为XML注释来输出,不进行编码过程,字段中不能有“--”字符串
"omitempty",若字段值为空、那么字段不会被输出到XML,空值有:false、0、nil指针,nil接口,任意长度为0的Slice、数组结构、Map结构或者string
",chardata",输出XML元素为character data而非element。
三 MessagePack格式
先看官方的定义:MessagePack是一种高效的二进制序列化格式。它允许您像JSON一样在多个语言之间交换数据。但是,它更快并且更小。
从官方定义中,可以有如下的结论:
- MessagePack是一个二进制序列化格式,因而它序列化的结果可以在多个语言间进行数据的交换。
- 从性能上讲,它要比json的序列化格式要好。
- 从结果大小上讲,它要比json的序列化结果要小。
但是官方并没有提MessagePack和google pb的对比,实际上从空间和时间两个方面对比,pb均要优于MessagePack,但pb相对MessagePack 的缺点是支持的语言种类比较少,需要编写专门的 .proto文件,使用上没有MessagePack方便
- 需要安装第三方包:go get github.com/vmihailenco/msgpack
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)
}
四 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
data,_:=ioutil.ReadFile("./fish.gob")
buf:=bytes.NewReader(data)
decoder:=gob.NewDecoder(buf)
decoder.Decode(&fishes)
fmt.Println(fishes)
}