序列化需求

内存中的map、slice、array以及各种对象,如何保存到一个文件中? 如果是自己定义的结构体的实 例,如何保存到一个文件中? 如何从文件中读取数据,并让它们在内存中再次恢复成自己对应的类型的实例?

要设计一套协议,按照某种规则,把内存中数据保存到文件中。文件是一个字节序列,所以必须把数据 转换成字节序列,输出到文件。这就是序列化。 反之,从文件的字节序列恢复到内存并且还是原来的类 型,就是反序列化。

定义

serialization 序列化:将内存中对象存储下来,把它变成一个个字节。转为 二进制数据。

deserialization 反序列化:将文件的一个个字节恢复成内存中对象。从二进制数据中恢复。

序列化保存到文件就是持久化。

字符序列化:JSON、XML等。

二进制序列化:Protocol Buffers、MessagePack等。

可以将数据序列化后持久化,或者网络传输;也可以将从文件中或者网络接收到的字节序列反序列化。可以把数据和二进制序列之间的相互转换称为二进制序列化、反序列化,把数据和字符序列之间的相互转换称为字符序列化、反序列化。

JSON的数据类型

JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于1999年发布的 ES3 (ECMAScript是w3c组织制定的JavaScript规范)的一个子集,采用完全独立于编程语言的文本格 式来存储和表示数据。应该说,目前JSON得到几乎所有浏览器的支持。官网:http://json.org/

JSON是字符串,是文本。JavaScript引擎可以将这种字符串解析为某类型的数据。

object类型

无序键值对的集合

格式: {key1:value1, ... ,keyn:valulen} key必须是一个字符串,需要双引号。 value可以是任意合法的值。

array类型

有序的值的集合

格式:[val1,...,valn]

value类型

双引号引起来的字符串、数值、true和false、null、对象、数组,这些都是值

string类型

由双引号包围起来的任意字符的组合,可以有转义字符。

number类型

有正负,有整数、浮点数。

json包

Go标准库中提供了 encoding/json 包,内部使用了反射技术,效率较为低下。官网https://go.dev/blo g/json

json.Marshal(v any) ([]byte, error),将v序列化成字符序列(本质上也是字节序列),这个过程称 为Encode

json.Unmarshal(data []byte, v any) error,将字符序列data反序列化为v,这个过程称为Decode

基本类型序列化:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    // 定义一个any切片类型的变量,将各种类型都填进去
    var data = []any{"牛逼abc", 123, 3.14, true, false, nil,
        map[int]string{},
        [2]int{1, 11}, []string{"a", "b", "c"},
    }
    target := [][]byte{}
    for _, v := range data {
        // 对各种类型的数据进行序列化,需提供任意类型的参数,返回字节序列和
        // error。(v any) ([]byte, error)
        r, err := json.Marshal(v)
        if err != nil {
            fmt.Println(err)
        }
        // 序列化后,会得到字节序列
        fmt.Printf("%T %[1]v ==> %T %[2]v\n", v, r)
        target = append(target, r)
    }
    // target为字节序列类型的切片
    fmt.Println(target)
    fmt.Println("----反序列化----")
    // 对字节序列进行反序列化
    var result any
    for _, v := range target {
        // 提供两个参数,第一个为字节序列,第二个为任意类型,用于接收反序列化后的值,
        // 类似Scan,需要用&来接收,返回error类型。(data []byte, v any) error
        err := json.Unmarshal(v, &result)
        if err != nil {
            fmt.Println(err)
        }
        fmt.Printf("%T %[1]v ==> %T %[2]v\n", v, result)
    }
}
示例1

 下面为上面代码执行结果:

 各种类型数据被序列化就成了字节序列,也可以说转换成了字符串。转换的结果都是字符串,但是这些字符串一旦交给JavaScript引擎,它能把它们转换 成对应的数据类型。

根据反序列化结果,从字符串(字节序列)反序列化为Go的某类型数据。JSON字符串中,数值被转换成了Go的float64类型;true、false转成了bool型; null转成了nil;字符串转成了string;数组转成了[]interface{}

结构体序列化:

package main

import (
    "encoding/json"
    "fmt"
)

type Animal struct {
    // 对于使用 json.Marshal 进行序列化的字段,它们必须是公开的(首字母大写)
    Name string
    Age  int
}

func main() {
    p0 := &Animal{"Tom", 20}
    r, err := json.Marshal(p0)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%T %[1]v ==> %T %[2]v\n", p0, r)
    fmt.Printf("%v\n", string(r))
    // 对其进行反序列化
    var v0 any
    json.Unmarshal(r, &v0)
    // 反序列化后,成了map了
    fmt.Printf("%T %[1]v\n", v0)
    // 可定义期望的类型的一个变量来进行接收,这样就变成期望的类型了
    var v1 Animal
    json.Unmarshal(r, &v1)
    fmt.Printf("%T %[1]v\n", v1)
}
示例2

下面是上面代码执行结果:

 切片序列化:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    s0 := make([]int, 3)
    r, err := json.Marshal(s0)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%T %[1]v ==> %T %[2]v\n", s0, r)
    var v0 interface{}
    err = json.Unmarshal(r, &v0)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%T %[1]v\n", v0)
    var v1 []int
    json.Unmarshal(r, &v1)
    fmt.Printf("%T %[1]v\n", v1)
}
示例3

下面是上面代码执行结果:

JSON序列化的Go实现效率较低,由此社区和某些公司提供大量开源的实现,例如easyjson、jsoniter、 sonic等。基本使用方式都兼容官方实现。 

结构体字段标签

结构体的字段可以增加标签tag以供序列化、反序列化时使用

在字段类型后,可以跟反引号引起来的一个标签,用json为key,value用双引号引起来写,key与value直接使用冒号,这个标签中不要加入多余空格,否则语法错误。 例1:Name string `json:"name"`,这个例子序列化后得到的属性名为name,json表示json库使用;双引号内第一个参数用来指定字段转换使用的名称,多个参数使用逗号隔开

例2:Name string `json:"name,omitempty"`,omitempty为序列化时忽略空值,也就是该字段不序列化;空值为false、0、空数组、空切片、空map、空串、nil空指针、nil接口值 空数组、空切片、空串、空map,长度len为0,也就是容器没有元素。

如果使用 - ,该字段将被忽略 Name string `json:"-"`,序列化后没有该字段,反序列化也不会转换该字段,Name string `json:"-,"`,序列化后该字段显示但名为 "-" ,反序列化也会转换该字段

多标签使用空格间隔:Name string `json:"name,omitempty" msgpack:"myname"`

MessagePack

MessagePack是一个基于二进制高效的对象序列化类库,可用于跨语言通信。 它可以像JSON那样,在 许多种语言之间交换结构对象。但是它比JSON更快速也更轻巧。 支持Python、Ruby、Java、C/C++、 Go等众多语言。宣称比Google Protocol Buffers还要快4倍。MessagePack: It's like JSON. but fast and small. (msgpack.org),其基本使用方法和json包类似。

 

package main

import (
    "encoding/json"
    "fmt"

    "github.com/vmihailenco/msgpack/v5"
)

type Animal struct {
    // 标签的各种写法
    Name string `json:"name"`
    Age  int    `msgpack:"a"`
    // 在Color处的json标签对High字段进行空值忽略,结果发现Color字段也有效
    Color  string `json:"High,omitempty" msgpack:"-"`
    Weight string `msgpack:"-"`
    High   float64
}

func main() {
    p0 := &Animal{Name: "Tom", Age: 20}
    r0, _ := json.Marshal(p0)
    r1, _ := msgpack.Marshal(p0)
    fmt.Printf("%T %[1]v ==> %T %[2]v\n", p0, r0)
    fmt.Printf("%T %[1]v ==> %T %[2]v\n", p0, r1)
    fmt.Println(string(r0), string(r1))
    // 反序列化
    var v0 any
    json.Unmarshal(r0, &v0)
    fmt.Printf("%T %[1]v\n", v0)
    msgpack.Unmarshal(r0, &v0)
    fmt.Printf("%T %[1]v\n", v0)
    var v1 Animal
    json.Unmarshal(r0, &v1)
    fmt.Printf("%T %[1]v\n", v1)
    msgpack.Unmarshal(r0, &v1)
    fmt.Printf("%T %[1]v\n", v1)
}
示例4

下面是上面代码执行结果:

 

posted on 2023-07-10 15:29  自然洒脱  阅读(335)  评论(0编辑  收藏  举报