Skip to content

序列化和反序列化

作者: ryan 发布于: 11/10/2025 更新于: 11/10/2025 字数: 0 字 阅读: 0 分钟

一、基本概念

定义

**序列化(Serialization)**是将内存中的数据结构(可以理解为“立体结构”,如链表、哈希表、结构体等)转化为线性字节序列(通常为二进制序列或字符序列)的过程。

**反序列化(Deserialization)**是将线性字节序列还原为内存中对应数据结构的过程。

序列化和反序列化的目的是确保数据在存储或传输后能够被正确还原为原始数据类型。

为什么需要序列化

序列化作为一种关键的数据处理机制,其核心价值在于解决内存数据的持久化存储、网络传输的格式兼容性以及跨环境数据一致性三大问题。

内存数据持久化存储:内存中的数据在程序关闭或掉电后会丢失,需通过序列化存储到持久化设备(如硬盘、U盘、光盘)以实现数据保存。

数据网络传输:网络传输要求数据以线性字节序列的形式发送,序列化将复杂数据结构转化为适合传输的格式。

跨进程/跨节点通信:不同进程或节点间的数据交换需依赖统一的数据表示形式。序列化通过标准化转换规则,确保发送方与接收方对数据的解析一致,从而维护数据的完整性和可传递性。例如远程服务调用时,序列化后的字节流可在接收端反序列化为语义相同的对象实例。

注意:

序列化后的字节序列必须能通过反序列化还原为原始数据类型,但不保证内存地址一致。

序列化协议必须保证数据完整性,丢失数据或无法还原的协议是无效的。

二、序列化和反序列化的实现方式

序列化协议

协议是一套规则,定义了如何将数据结构转化为字节序列以及如何还原。必须同时支持序列化和反序列化,确保数据完整性和类型一致性。

协议类型

文本序列化

文本序列化的典型协议是 JSON(JavaScript Object Notation) 。它的核心优势在于语法简单、可读性强且支持广泛,尤其在 Web 开发中广泛应用,主流浏览器均内置原生 JSON 解析支持。其格式设计紧凑,相较于 XML 等标签语言显著减少冗余(无需闭合标签),能直接表达多种基础数据类型(如整数、字符串、布尔值、数组及键值对对象)。

当然,JSON 也存在局限性:相比 Protobuf 等二进制序列化方案,其基于文本的编码方式通常占用更大存储与传输空间。这是由于非文本数据(如数值、布尔值)需转换为字符串格式存储,且字段名称重复出现导致冗余。

二进制序列化

二进制序列化的优点是占用字节少、处理效率高,但序列化后的数据为二进制格式,人类无法直接阅读解析。典型的二进制序列化协议包括:

  1. MessagePack 一种轻量级二进制序列化协议,支持多语言,学习曲线平缓,序列化效率高,并自带数据压缩能力,适合对性能要求较高且需跨语言交互的场景。
  2. Protocol Buffers(Protobuf) 由 Google 开发的二进制序列化协议,适用于需高度定制数据结构与通信格式的场景。其优势在于显著节省存储空间与传输带宽,序列化与反序列化效率高;缺点是序列化结果不具备可读性,需依赖专用工具或库进行解析,且学习成本较高。

许多编程语言(如Go、Python、Java)为内置类型(如整数、字符串、切片、Map、结构体)提供了序列化方案。自定义类型可能需要开发者实现序列化/反序列化逻辑,不同语言的序列化方案可能导致生成的字节序列大小不同,效率也不同。

协议类型优点缺点适用场景
JSON文本人类可读,广泛支持,简单易用占用字节多,效率较低Web开发、API数据交换
MessagePack二进制紧凑高效,支持多语言,学习曲线低不可读,调试复杂高性能场景、跨语言传输
Protocol Buffers二进制高效,强类型,支持复杂结构学习曲线陡,需定义Schema大规模分布式系统、微服务

三、JSON序列化

JSON(JavaScript Object Notation)是一种轻量级、语言无关的数据交换格式, 由 Douglas Crockford 于 2001 年发现,2002 年在 json.org 公布。

它源于 ECMAScript 3(1999)的对象字面量语法,但自诞生之日起就独立于任何编程语言。截至 2025 年,JSON 仍是全球最广泛使用的数据格式,几乎所有编程语言和浏览器都原生支持。

JSON 数据格式(详解版)

What is JSON? and What is the Use in Programming? - Blog for Learning

JSON 的数据类型

JSON 的语法简单,易于阅读和编写,同时被多种编程语言支持。JSON 支持以下几种基本数据类型,下面将逐一讲解。

字符串(String)

字符串是 JSON 中最常用的数据类型,用于表示文本数据。字符串必须用双引号(")括起来,支持 Unicode 字符和转义字符。

特点

必须用双引号括起来,单引号无效。

支持转义字符,例如:

\" 表示双引号

\\ 表示反斜杠

\n 表示换行符

示例:

json
{
  "name": "Alice",  表示一个键值对,其中值是字符串 Alice 
  "greeting": "Hello, \"World\"!", 转义字符允许在字符串中包含特殊字符
  "unicode": "\u4E2D\u6587" Unicode 转义支持国际化字符
}

数字(Number)

JSON 中的数字可以是整数或浮点数,不区分整数和浮点数的类型。JSON 不支持非数值(NaN)或无穷大(Infinity)。

特点

  • 支持正数、负数、零。
  • 支持小数(浮点数)和科学计数法(如 1.23e-4)。
  • 不需要引号包裹。

示例

json
{
  "age": 25,
  "height": 1.75,
  "score": -10,
  "exponential": 1.23e-4
}

25 是一个整数,1.75 是一个浮点数。

科学计数法(如 1.23e-4)表示 0.000123。

3. 布尔值(Boolean)

JSON 支持布尔值,只有两个可能的值:true 和 false。

特点

  • 不需要引号包裹。
  • 常用于表示开关状态或条件。

示例

json
{
  "isStudent": true,
  "isEmployed": false
}

4. 空值(Null)

JSON 使用 null 表示空值,表示一个不存在或无意义的值。

特点

  • null是一个单独的值,不是字符串或数字。
  • 常用于表示缺失的数据。

示例

json
{
  "address": null,
  "phone": "123-456-7890"
}

5. 对象(Object)

JSON 对象是一个无序的键值对集合,用花括号 {} 包裹,键和值之间用冒号 : 分隔,键值对之间用逗号 , 分隔。

特点

  • 键必须是字符串类型,且用双引号包裹。
  • 值可以是任意 JSON 数据类型(字符串、数字、布尔值、null、对象、数组)。
  • 对象可以嵌套,形成复杂的数据结构。

示例

json
{
  "person": {
    "name": "Bob",
    "age": 30,
    "isStudent": false,
    "address": {
      "street": "123 Main St",
      "city": "New York"
    }
  }
}

person 是一个对象,包含多个键值对。

address 是嵌套的对象,展示了 JSON 的层级结构。

6. 数组(Array)

JSON 数组是一个有序的值集合,用方括号 [] 包裹,值之间用逗号 , 分隔。

特点

  • 数组中的值可以是任意 JSON 数据类型(字符串、数字、布尔值、null、对象、数组)。
  • 数组支持嵌套。

示例

bash
{
  "scores": [95, 80, 90],
  "friends": ["Alice", "Bob", "Charlie"],
  "mixed": [1, "two", true, null, {"name": "Dave"}]
}

scores 是一个数字数组,friends 是一个字符串数组。

mixed 展示了数组可以包含多种数据类型。

四、Go语言中的序列化

encoding/json 包简介

Go语言的encoding/json包是标准库中用于处理JSON数据的核心工具包,提供了将Go数据结构与JSON格式之间相互转换的功能,即序列化(编码,Encode)和反序列化(解码,Decode)。该包通过反射(reflection)实现通用性和灵活性,支持几乎所有Go数据类型(基本类型、结构体、切片、映射等)的序列化与反序列化。但由于依赖反射,性能相对较低,适合中小规模数据处理,高性能场景可考虑社区优化库(如jsoniter、sonic)。

主要功能

序列化:通过json.Marsha将Go数据结构转换为JSON格式的字节序列[]byte

反序列化:通过json.Unmarshal将JSON格式的字节序列转换回Go数据结构。

核心函数

json.Marshal(v any) ([]byte, error)

功能:将Go数据结构(v)序列化为JSON格式的字节序列。并返回JSON字节序列[]byte和错误。

使用场景:将内存中的数据结构(如结构体、切片、映射)转化为JSON字符串以存储或传输。

json.Unmarshal(data []byte, v any) error

功能:将JSON格式的字节序列data反序列化为Go数据结构v。若解析失败返回错误

使用场景:从文件或网络读取JSON数据并还原为Go数据结构。

基本类型序列化

序列化

go
package main

import (
	"encoding/json"
	"fmt"
)

func main() {

	var data = []any{ //定义一个类型为 []any 的切片
		123, "abc", true, false, nil, 9.12, //包含多种 Go 基本类型
		[5]int{1, 2, 3, 4, 5},                          //Go数组类型 转换成json 的数组
		[]int{88, 89, 90},                              //Go切片类型 转换成json 的数组
		map[string]int{"a": 1, "b": 2, "c": 3, "d": 4}, //map 转换成 json 的对象类型
	}

	var target = make([][]byte, 0, len(data)) 
    //使用 make 初始化,初始长度为 0,容量为 len(data),以避免动态扩容,提高性能。
	for i, v := range data {
		b, err := json.Marshal(v) //使用 json.Marshal 将每个元素转换为 JSON 格式。
		if err != nil {
			fmt.Println("error:", err.Error()) //error接口默认不是调用stringer的string方法,而是优先使用Error
			continue
		}
		fmt.Printf("%d: %T %[2]v => %T %[4]v\n", i, v, b, string(b))
		target = append(target, b)
	}

}

输出

bash
0: int 123 => []uint8 123
1: string abc => []uint8 "abc"
2: bool true => []uint8 true
3: bool false => []uint8 false
4: <nil> <nil> => []uint8 null
5: float64 9.12 => []uint8 9.12
6: [5]int [1 2 3 4 5] => []uint8 [1,2,3,4,5]
7: []int [88 89 90] => []uint8 [88,89,90]
8: map[string]int map[a:1 b:2 c:3 d:4] => []uint8 {"a":1,"b":2,"c":3,"d":4}

var target = make([][]byte, 0, len(data))

encoding/json 包的 json.Marshal 函数会将数据序列化为 JSON 格式的字节切片[]byte

我们将多个 JSON 序列化的结果[]byte存储在 [][]byte 中,target 就相当于一个集合,包含多个 JSON 序列化的字节序列。如果 data 有 9 个元素,target 最终可能包含 9 个 []byte,每个 []byte 是对应元素的 JSON 表示(例如 [49 50 51] 表示 "123"[34 97 98 99 34] 表示 "abc" 等)。

通过设置容量为 len(data),程序预分配了足够的内存空间,避免了动态扩容的开销(扩容通常需要重新分配更大的数组并拷贝数据)。

反序列化

遍历 target 中的每个[]byte元素。

go
	for i, v := range target {
		var t any
		err := json.Unmarshal(v, &t)
		if err != nil {
			continue
		}
		fmt.Printf("%d %T: %[2]v => %T %[3]v\n", i, v, t)
	}

调用 json.Unmarshal 函数,将字节切片 v(JSON 数据)反序列化为 Go 的值,存储到 t 中。

json.Unmarshal 需要一个指针&t,因为它会修改 t 的值。并且会根据 v 的 JSON 内容自动推断类型

JSON 字符串(如 "abc")解析为 Go 的 string

JSON 数字(如 42)解析为 float64(JSON 数字默认解析为 float64)。

JSON 对象(如 {"name": "apple"})解析为 map[string]interface{}

JSON 数组(如 [1, 2, 3])解析为 []interface{}

JSON 反序列化导致的精度损失

在 Go 中,json.Unmarshal 将 JSON 数据解析为 Go 类型时,可能会因为 JSON 本身的弱类型特性导致精度或类型信息的丢失。

数字类型的精度损失

例如 JSON 没有区分整数和浮点数,所有数字都被解析为 float64.如果原始数据是一个大整数(例如int64uint64),反序列化为 float64 导致精度丢失

复杂结构体的类型信息丢失

如果 target 中的 []byte 是复杂结构体序列化的 JSON 数据,使用 var t any 反序列化会导致类型信息丢失,t 通常被解析为 map[string]interface{}[]interface{}

例如,一个 Go 结构体struct { Name string; Age int }序列化为 JSON 后,反序列化到 any 类型后变成Value: map[Age:30 Name:Alice] 原始的 Person 结构体类型丢失,变成了map[string]interface{}

结构体序列化

go
package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	var data = Person{
		Name: "John",
		Age:  42,
	}

	b, err := json.Marshal(data)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%+v\n", data)            //这是Person的实例
	fmt.Printf("%v, %s\n", b, string(b)) //这是字符串

	//反序列化
	var b1 = []byte(`{"Name":"John","Age":42}`) //

	var p Person                 //知道目标的类型
	err = json.Unmarshal(b1, &p) //通过指针填充结构

	if err != nil {
		panic(err)
	}
	fmt.Printf("%T %+[1]v\n", p)

	var i interface{}
	err = json.Unmarshal(b1, &i)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%T %+[1]v\n", i)

}

json.Unmarshal(b1, &p)b1 中的 JSON 数据反序列化到 p 中。&p 传递 p 的指针,因为 json.Unmarshal 需要通过指针修改变量内容。JSON 的键(NameAge)会匹配 Person 结构体的字段名(大小写敏感)。

p 被填充为 Person{Name: "John", Age: 42}

切片序列化

未知类型:将 JSON 数据反序列化到空接口 (interface{})。

已知类型:将 JSON 数据反序列化到 []Person类型。

go
package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	var data = []Person{
		{Name: "Bob", Age: 30},
		{Name: "Alice", Age: 27},
	}
	// 使用 json.Marshal 将 data 序列化为 JSON 格式的字节切片
	b, err := json.Marshal(data)
	if err != nil {
		panic(err)
	}

	fmt.Println(b, string(b)) // 请问序列化后的字符串中,还有类型吗?有什么?

	// - b 是字节切片([]byte),显示为字节数组
	// - string(b) 将字节切片转换为字符串,显示 JSON 字符串

	// 反序列化:未知类型
	// 定义一个空接口变量,用于接收未知结构的 JSON 数据
	var i interface{}
	err = json.Unmarshal(b, &i)
	if err != nil {
		panic(err)
	}
	// - %T 显示变量 i 的类型:[]interface{}
	// - %+v 显示详细值:JSON 数组解析为 []interface{},每个元素是 map[string]interface{}
	fmt.Printf("%T: %+[1]v\n", i)

	// 反序列化:已知目标类型
	// 定义一个新的 JSON 字节切片,键名使用小写("name" 和 "age")
	var b1 = []byte(`[{"name":"Bob","age":30},{"name":"Alice","age":27}]`)
	var j []Person
	err = json.Unmarshal(b1, &j)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%T: %+[1]v\n", j)

	// 输出示例:[]main.Person: [{Name:Bob Age:30} {Name:Alice Age:27}]
	// 说明:
	// - JSON 键名("name"、"age")与 Person 字段(Name、Age)忽略大小写匹配
	// - 反序列化直接恢复为 []Person 结构,字段值正确赋值
}

输出

go
[91 123 34 78 97 109 101 34 58 34 66 111 98 34 44 34 65 103 101 34 58 51 48 125 44 123 34 78 97 109 101 34 58 34 65 108 105 99 101 34 44 34 65 103 101 34 58 50 55 125 93] [{"Name":"Bob","Age":30},{"Name":"Alice","Age":27}]
[]interface {}: [map[Age:30 Name:Bob] map[Age:27 Name:Alice]]
[]main.Person: [{Name:Bob Age:30} {Name:Alice Age:27}]

序列化后的 JSON 字符串为:[{"Name":"Bob","Age":30},{"Name":"Alice","Age":27}]

这是一个 JSON 数组([]),包含两个 JSON 对象({})。

每个对象有两个键值对:

"Name":"Bob" "Age":30"

"Name":"Alice" "Age":27"

字段标签

在 Go 语言中,结构体的字段可以附加标签(Tag),用于序列化或反序列化时指定字段的行为。

标签写在字段类型后,使用反引号括起来,值通常用双引号括起来,多个参数以逗号:分隔。

不同标签之间用空格分隔,但标签内部不能包含多余空格,否则会导致语法错误。

示例

go
type User struct {
    Name string `json:"name"`
}

json:"name": 表示该字段在 JSON 序列化后属性名为 name

标签的常见用法

指定字段名称

通过 json 标签的第一个参数,可以指定字段在序列化后的 JSON 属性名称。

go
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name string `json:"username"`
    Age  int    `json:"age"`
}

func main() {
    user := User{Name: "Alice", Age: 25}
    data, _ := json.Marshal(user)
    fmt.Println(string(data))
    // 输出: {"username":"Alice","age":25}
}

Name 字段序列化为 username

Age 字段序列化为 age

忽略空值

使用 json:"name,omitempty",表示当字段值为空时,序列化后忽略该字段。

go
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name    string   `json:"name,omitempty"`
    Age     int      `json:"age,omitempty"`
    Hobbies []string `json:"hobbies,omitempty"`
}

func main() {
    user := User{Name: "", Age: 0, Hobbies: []string{}}
    data, _ := json.Marshal(user)
    fmt.Println(string(data))
    // 输出: {}
}

Name 是空字符串,Age 是 0,Hobbies 是空切片,均被忽略。

结果 JSON 为空对象 {}

忽略字段

使用 json:"-",表示序列化和反序列化时完全忽略该字段。

go
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name     string `json:"name"`
    Password string `json:"-"`
}

func main() {
    user := User{Name: "Alice", Password: "secret"}
    data, _ := json.Marshal(user)
    fmt.Println(string(data))
    // 输出: {"name":"Alice"}

    // 反序列化
    jsonStr := `{"name":"Bob","password":"secret"}`
    var newUser User
    json.Unmarshal([]byte(jsonStr), &newUser)
    fmt.Println(newUser)
    // 输出: {Bob }
}

Password 字段被忽略,序列化后不出现。

反序列化时,输入 JSON 中的 password 字段不会赋值给 Password

使用特殊名称

使用 json:"-,omitempty",表示字段序列化后属性名为 -,但仍会序列化和反序列化。

go
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name string `json:"name"`
    Code string `json:"-,omitempty"`
}

func main() {
    user := User{Name: "Alice", Code: "123"}
    data, _ := json.Marshal(user)
    fmt.Println(string(data))
    // 输出: {"name":"Alice","-":"123"}

    // 反序列化
    jsonStr := `{"name":"Bob","-":"456"}`
    var newUser User
    json.Unmarshal([]byte(jsonStr), &newUser)
    fmt.Println(newUser)
    // 输出: {Bob 456}
}

Code 字段序列化为属性名 -

反序列化时,JSON 中的-属性会映射到 Code 字段。

多标签支持

多个标签(如 jsonmsgpack)可以通过空格分隔,分别指定不同序列化库的行为。

go
package main

import (
    "encoding/json"
    "fmt"
    "github.com/vmihailenco/msgpack/v5"
)

type User struct {
    Name string `json:"name" msgpack:"myname"`
}

func main() {
    user := User{Name: "Alice"}

    // JSON 序列化
    jsonData, _ := json.Marshal(user)
    fmt.Println("JSON:", string(jsonData))
    // 输出: JSON: {"name":"Alice"}

    // MsgPack 序列化
    msgpackData, _ := msgpack.Marshal(user)
    var newUser User
    msgpack.Unmarshal(msgpackData, &newUser)
    fmt.Println("MsgPack:", newUser)
    // 输出: MsgPack: {Alice}
}

JSON 序列化使用 name 作为字段名。

MsgPack 序列化使用 myname 作为字段名。

小结

  1. 标签中不能包含多余的空格,例如 json: "name" 会导致语法错误,正确写法是 json:"name"

  2. omitempty 只会忽略空值;像 0false 这样的非空零值仍会被序列化,除非明确使用 omitempty

  3. 使用 json:"-" 会让字段完全不参与序列化和反序列化,适合用来保护敏感数据。

  4. 多个标签之间用空格分隔,这在字段需要支持多种序列化格式时非常有用。

MessagePack

MessagePack 是一个高效的二进制序列化库,设计用于跨语言数据交换。它与 JSON 类似,支持结构化对象的序列化与反序列化,但相较于 JSON,MessagePack 具有以下优势:

  • 更快速:序列化和反序列化速度更快,宣称比 Google Protocol Buffers 快约 4 倍。
  • 更轻巧:生成的二进制数据体积更小,适合网络传输和存储。
  • 跨语言支持:支持多种编程语言,包括 Python、Ruby、Java、C/C++、Go、JavaScript 等。

官方网站: https://msgpack.org/

文档: https://msgpack.uptrace.dev/

在 Go 项目中使用 MessagePack,需要安装 Go 的 MessagePack 库。

go
go get github.com/vmihailenco/msgpack/v5

MessagePack 的使用方式与 Go 的 encoding/json 包类似,支持将 Go 数据结构序列化为二进制数据,以及从二进制数据反序列化为 Go 数据结构。

go
package main

import (
	"fmt"
	"github.com/vmihailenco/msgpack/v5"
)

// 定义结构体,使用 msgpack 标签自定义字段名
type Person struct {
	Name string `json:"name" msgpack:"myname"`
	Age  int    `json:"age" msgpack:"myage"`
}

func main() {
	// 创建示例数据
	data := []Person{
		{Name: "Tom", Age: 16},
		{Name: "Jerry", Age: 32},
	}

	// 序列化:将 Go 数据结构转为 MessagePack 二进制格式
	b, err := msgpack.Marshal(data)
	if err != nil {
		panic(err)
	}
	fmt.Printf("序列化结果 (二进制): %v\n", b)
	fmt.Printf("序列化数据长度: %d 字节\n", len(b))
	fmt.Printf("二进制转字符串: %s\n", string(b))

	// 反序列化:将二进制数据还原为 Go 数据结构
	var result []Person
	err = msgpack.Unmarshal(b, &result)
	if err != nil {
		fmt.Println("反序列化错误:", err)
		return
	}
	fmt.Printf("反序列化结果 (%T): %+v\n", result, result)
}

输出:

go
序列化结果 (二进制): [146 130 166 109 121 110 97 109 101 163 84 111 109 165 109 121 97 103 101 16 130 166 109 121 110 97 109 101 165 74 101 114 114 121 165 109 121 97 103 101 32]
序列化数据长度: 41 字节
二进制转字符串 (仅供参考): ���myname�Tom�myage��myname�Jerry�myage 
反序列化结果 ([]main.Person): [{Name:Tom Age:16} {Name:Jerry Age:32}]

结构体定义

  • 使用 msgpack:"myname"msgpack:"myage" 标签自定义字段名,序列化时字段名会变为 mynamemyage,而不是 Go 结构体的字段名 NameAge
  • 标签类似于 JSON 的 json:"name",支持与 JSON 兼容的字段命名。

序列化

  • msgpack.Marshal 将 Go 数据结构(如结构体、切片、映射等)转换为 MessagePack 格式的二进制数据。
  • 返回的 b 是一个字节切片[]byte,表示紧凑的二进制数据。
  • len(b) 显示序列化后的数据长度,通常比 JSON 小。

反序列化

  • msgpack.Unmarshal 将二进制数据还原为指定的 Go 数据结构。
  • 需要传入目标变量的指针 &result,以便 MessagePack 填充数据。

与 JSON 和 Protocol Buffers 的对比

特性MessagePackJSONProtocol Buffers
格式二进制文本二进制
数据体积较大
序列化速度非常快较慢
可读性不可读可读不可读
跨语言支持广泛极广泛广泛
模式定义无需无需需要(.proto 文件)