目录
- [🟠 Go 入门到精通:编码与序列化](#🟠 Go 入门到精通:编码与序列化)
-
- [1. encoding/json:JSON 编解码核心](#1. encoding/json:JSON 编解码核心)
-
- 基本用法
- json.MarshalIndent:格式化输出
- [Map 与 JSON 互转](#Map 与 JSON 互转)
- [2. 结构体 Tag 的艺术](#2. 结构体 Tag 的艺术)
-
- [omitempty 的零值陷阱](#omitempty 的零值陷阱)
- 嵌套结构与匿名字段
- [3. 自定义序列化行为](#3. 自定义序列化行为)
-
- [实现 Marshaler / Unmarshaler 接口](#实现 Marshaler / Unmarshaler 接口)
- 时间类型的处理
- [4. json.RawMessage:延迟解析](#4. json.RawMessage:延迟解析)
- [5. 流式解析大文件](#5. 流式解析大文件)
-
- [逐行 JSON(NDJSON)](#逐行 JSON(NDJSON))
- [逐行流(NDJSON / JSON Lines)](#逐行流(NDJSON / JSON Lines))
- [6. json.NewEncoder 性能优势](#6. json.NewEncoder 性能优势)
-
- 性能基准对比
- [SetEscapeHTML:控制 HTML 转义](#SetEscapeHTML:控制 HTML 转义)
- [7. encoding/xml:XML 处理](#7. encoding/xml:XML 处理)
-
- 基本序列化
- [XML vs JSON 标签对比](#XML vs JSON 标签对比)
- [流式 XML 解析](#流式 XML 解析)
- [8. Protocol Buffers 入门](#8. Protocol Buffers 入门)
-
- [安装 protoc 编译器](#安装 protoc 编译器)
- [定义 .proto 文件](#定义 .proto 文件)
- [生成 Go 代码并使用](#生成 Go 代码并使用)
- [Protobuf 核心优势](#Protobuf 核心优势)
- [9. encoding/gob:Go 原生二进制编码](#9. encoding/gob:Go 原生二进制编码)
-
- [Gob 的特性](#Gob 的特性)
- [Gob 的独特能力](#Gob 的独特能力)
- [10. 序列化方案性能对比](#10. 序列化方案性能对比)
- [11. 小结与思考](#11. 小结与思考)
-
- [💬 互动思考](#💬 互动思考)
🟠 Go 入门到精通:编码与序列化
📅 更新于 2026年7月 | ✍️ 原创文章,转载请注明出处
在分布式系统和微服务架构中,数据序列化是贯穿始终的核心环节------无论是 RESTful API 的 JSON 响应、gRPC 的 Protobuf 消息、还是 Go 服务间的 RPC 调用,都离不开高效可靠的编解码方案。Go 语言标准库提供了丰富的序列化支持,从 encoding/json 到 encoding/gob,配合第三方工具如 Protocol Buffers,能够覆盖绝大多数工程场景。本文带你系统掌握 Go 中的编码与序列化技术。
1. encoding/json:JSON 编解码核心
encoding/json 是 Go 标准库中最常用的序列化包,提供了 Marshal(序列化) 和 Unmarshal(反序列化) 两大核心功能。
基本用法
go
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
// 序列化:Go 结构体 → JSON 字节流
u := User{ID: 1, Name: "布朗克", Email: "blangke@example.com"}
data, err := json.Marshal(u)
if err != nil {
panic(err)
}
fmt.Println(string(data))
// 输出:{"id":1,"name":"布朗克","email":"blangke@example.com"}
// 反序列化:JSON 字节流 → Go 结构体
raw := `{"id":2,"name":"小明","email":"xiaoming@example.com"}`
var u2 User
err = json.Unmarshal([]byte(raw), &u2)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", u2)
// 输出:{ID:2 Name:小明 Email:xiaoming@example.com}
}
json.MarshalIndent:格式化输出
调试时经常需要可读性更好的格式化 JSON:
go
data, err := json.MarshalIndent(u, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(data))
// {
// "id": 1,
// "name": "布朗克",
// "email": "blangke@example.com"
// }
Map 与 JSON 互转
go
// Map → JSON
m := map[string]interface{}{
"name": "张三",
"age": 28,
"tags": []string{"Go", "Rust"},
}
data, _ := json.Marshal(m)
fmt.Println(string(data))
// {"age":28,"name":"张三","tags":["Go","Rust"]}
// JSON → Map
raw := `{"name":"李四","score":95}`
var result map[string]interface{}
json.Unmarshal([]byte(raw), &result)
fmt.Println(result["name"]) // 李四
fmt.Println(result["score"]) // 95 (float64!)
⚠️ 注意 :反序列化到
map[string]interface{}时,JSON 数字默认被解析为float64,这是 Go 初学者常踩的坑。
2. 结构体 Tag 的艺术
Go 的结构体 tag 是序列化行为控制的"指挥中心"。encoding/json 支持丰富的 tag 选项:
| Tag 选项 | 含义 | 示例 |
|---|---|---|
json:"fieldname" |
指定 JSON 字段名 | json:"user_name" |
json:"-" |
忽略该字段,不参与序列化 | json:"-" |
json:"field,omitempty" |
零值时省略该字段 | json:"email,omitempty" |
json:"field,string" |
将数值转为 JSON 字符串 | json:"id,string" |
json:"field,omitempty,string" |
组合使用,逗号分隔 | json:"count,omitempty,string" |
omitempty 的零值陷阱
go
type Profile struct {
Name string `json:"name,omitempty"` // "" 时省略
Age int `json:"age,omitempty"` // 0 时省略
Score float64 `json:"score,omitempty"` // 0.0 时省略
Tags []string `json:"tags,omitempty"` // nil 或空切片时省略
Married bool `json:"married,omitempty"` // false 时省略 ⚠️
Email *string `json:"email,omitempty"` // nil 时省略 ✅
}
🔑 技巧 :对于
bool字段,推荐使用指针类型*bool来区分"未设置"和"false"。
嵌套结构与匿名字段
go
type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type Person struct {
Name string `json:"name"`
Address Address `json:"address"` // 嵌套结构
// 扁平化:使用 ,inline 或直接匿名嵌入(Go 1.26 支持)
}
p := Person{Name: "Alice", Address: Address{City: "北京", Country: "中国"}}
data, _ := json.Marshal(p)
// {"name":"Alice","address":{"city":"北京","country":"中国"}}
3. 自定义序列化行为
Go 通过接口机制允许类型自定义序列化逻辑。
实现 Marshaler / Unmarshaler 接口
go
type Color struct {
R, G, B uint8
}
// 自定义序列化:输出 "#RRGGBB" 格式
func (c Color) MarshalJSON() ([]byte, error) {
hex := fmt.Sprintf("\"#%02X%02X%02X\"", c.R, c.G, c.B)
return []byte(hex), nil
}
// 自定义反序列化:解析 "#RRGGBB" 格式
func (c *Color) UnmarshalJSON(data []byte) error {
var hex string
if err := json.Unmarshal(data, &hex); err != nil {
return err
}
_, err := fmt.Sscanf(hex, "#%02X%02X%02X", &c.R, &c.G, &c.B)
return err
}
func main() {
c := Color{R: 255, G: 128, B: 64}
data, _ := json.Marshal(c)
fmt.Println(string(data)) // "#FF8040"
var c2 Color
json.Unmarshal([]byte(`"#00FF00"`), &c2)
fmt.Printf("R=%d G=%d B=%d\n", c2.R, c2.G, c2.B) // R=0 G=255 B=0
}
时间类型的处理
Go 中的时间序列化是高频场景:
go
type CustomTime struct {
time.Time
}
// 自定义时间格式
func (ct CustomTime) MarshalJSON() ([]byte, error) {
formatted := ct.Format("2006-01-02 15:04:05")
return json.Marshal(formatted)
}
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
t, err := time.Parse("2006-01-02 15:04:05", s)
if err != nil {
return err
}
ct.Time = t
return nil
}
4. json.RawMessage:延迟解析
json.RawMessage 本质是 []byte 的别名,允许你暂缓解析 JSON 片段,根据业务条件动态决定反序列化目标:
go
type Event struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"` // 延迟解析
}
type LoginPayload struct {
UserID int `json:"user_id"`
IP string `json:"ip"`
}
type PurchasePayload struct {
OrderID string `json:"order_id"`
Amount float64 `json:"amount"`
}
func handleEvent(raw string) error {
var event Event
if err := json.Unmarshal([]byte(raw), &event); err != nil {
return err
}
switch event.Type {
case "login":
var lp LoginPayload
if err := json.Unmarshal(event.Payload, &lp); err != nil {
return err
}
fmt.Printf("用户 %d 从 %s 登录\n", lp.UserID, lp.IP)
case "purchase":
var pp PurchasePayload
if err := json.Unmarshal(event.Payload, &pp); err != nil {
return err
}
fmt.Printf("订单 %s,金额 ¥%.2f\n", pp.OrderID, pp.Amount)
default:
fmt.Printf("未知事件类型: %s,原始数据: %s\n", event.Type, event.Payload)
}
return nil
}
💡 应用场景 :Webhook 回调、消息队列消费、API 网关路由等,根据
type字段动态选择处理逻辑。
json.Number:精确数字解析
使用 json.Decoder 配合 UseNumber() 可避免浮点数精度丢失:
go
raw := `{"id":9007199254740993,"price":19.99}`
// 方式1:使用 json.Number
decoder := json.NewDecoder(strings.NewReader(raw))
decoder.UseNumber()
var result map[string]interface{}
decoder.Decode(&result)
id, _ := result["id"].(json.Number).Int64()
fmt.Println(id) // 9007199254740993 ✅ 精确
5. 流式解析大文件
对于动辄 GB 级的 JSON 日志或数据文件,一次性 Unmarshal 会撑爆内存。json.Decoder 提供了流式解决方案:
逐行 JSON(NDJSON)
go
func processLargeJSON(r io.Reader) error {
decoder := json.NewDecoder(r)
// 读取开头的 '['
if _, err := decoder.Token(); err != nil {
return err
}
// 逐个解析数组元素
for decoder.More() {
var record map[string]interface{}
if err := decoder.Decode(&record); err != nil {
return err
}
// 处理每条记录
fmt.Printf("处理记录: %v\n", record["id"])
}
// 读取结尾的 ']'
if _, err := decoder.Token(); err != nil {
return err
}
return nil
}
逐行流(NDJSON / JSON Lines)
go
func processNDJSON(r io.Reader) error {
scanner := bufio.NewScanner(r)
lineNum := 0
for scanner.Scan() {
lineNum++
line := scanner.Bytes()
if len(line) == 0 {
continue
}
var record map[string]interface{}
if err := json.Unmarshal(line, &record); err != nil {
fmt.Printf("第 %d 行解析失败: %v\n", lineNum, err)
continue
}
// 处理记录...
}
return scanner.Err()
}
📊 性能提示 :处理 GB 级 JSON 文件时,
json.Decoder的内存占用几乎是常数级(约几 MB),而json.Unmarshal需要整个文件大小的 5-10 倍内存。
6. json.NewEncoder 性能优势
很多开发者习惯用 json.Marshal 写出数据后再 Write,而更高效的做法是直接用 json.NewEncoder:
go
// ❌ 低效:两次内存分配
data, _ := json.Marshal(obj)
w.Write(data)
// ✅ 高效:直接写入流,零额外分配
json.NewEncoder(w).Encode(obj)
性能基准对比
go
func BenchmarkMarshalWrite(b *testing.B) {
obj := largeStruct()
var buf bytes.Buffer
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf.Reset()
data, _ := json.Marshal(obj)
buf.Write(data)
}
}
func BenchmarkEncoder(b *testing.B) {
obj := largeStruct()
var buf bytes.Buffer
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf.Reset()
json.NewEncoder(&buf).Encode(obj)
}
}
// BenchmarkMarshalWrite-8 45230 26120 ns/op 12480 B/op 15 allocs/op
// BenchmarkEncoder-8 58240 20150 ns/op 4096 B/op 2 allocs/op
Encoder 不仅减少了内存分配次数,还内建了输出缓冲,对于 HTTP 响应写入、文件输出等场景是更优选择。
SetEscapeHTML:控制 HTML 转义
go
encoder := json.NewEncoder(os.Stdout)
encoder.SetEscapeHTML(false) // 不转义 <, >, & 等字符
encoder.SetIndent("", " ") // 设置缩进
encoder.Encode(obj)
7. encoding/xml:XML 处理
尽管 JSON 已统治 Web API,XML 在企业系统、SOAP 协议和配置文件领域仍有大量应用场景。
基本序列化
go
type Book struct {
XMLName xml.Name `xml:"book"`
ID string `xml:"id,attr"` // XML 属性
Title string `xml:"title"`
Author string `xml:"author"`
Year int `xml:"year,omitempty"`
}
book := Book{
ID: "b001",
Title: "Go语言程序设计",
Author: "布朗克",
}
data, _ := xml.MarshalIndent(book, "", " ")
fmt.Println(string(data))
// <book id="b001">
// <title>Go语言程序设计</title>
// <author>布朗克</author>
// </book>
XML vs JSON 标签对比
| 特性 | XML | JSON |
|---|---|---|
| 标签语法 | xml:"name,attr,omitempty" |
json:"name,omitempty,string" |
| 属性支持 | ✅ ,attr |
❌ 不支持 |
| 命名空间 | ✅ xml:"ns name" |
❌ 不支持 |
| 自闭合标签 | ✅ 自动处理 | ❌ 无此概念 |
| 注释 | xml:"comment" |
❌ 不支持 |
| 字符数据 | xml:",chardata" |
❌ 无此概念 |
流式 XML 解析
go
decoder := xml.NewDecoder(file)
for {
token, err := decoder.Token()
if err == io.EOF {
break
}
switch se := token.(type) {
case xml.StartElement:
if se.Name.Local == "item" {
var item Item
decoder.DecodeElement(&item, &se)
// 处理 item
}
}
}
8. Protocol Buffers 入门
Protocol Buffers(protobuf)是 Google 开发的高效二进制序列化协议,已成为 gRPC 生态的标准数据格式。
安装 protoc 编译器
bash
# macOS
brew install protobuf
# Linux
apt-get install protobuf-compiler
# 安装 Go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
定义 .proto 文件
protobuf
syntax = "proto3";
package user;
option go_package = "example.com/userpb";
message User {
int64 id = 1;
string name = 2;
string email = 3;
repeated string tags = 4; // 字符串数组
Address address = 5; // 嵌套消息
}
message Address {
string city = 1;
string country = 2;
}
生成 Go 代码并使用
bash
protoc --go_out=. --go_opt=paths=source_relative user.proto
go
import pb "example.com/userpb"
func main() {
user := &pb.User{
Id: 1,
Name: "布朗克",
Email: "blangke@example.com",
Tags: []string{"Go", "Rust"},
Address: &pb.Address{
City: "深圳",
Country: "中国",
},
}
// 序列化
data, err := proto.Marshal(user)
if err != nil {
panic(err)
}
fmt.Printf("protobuf 大小: %d bytes\n", len(data)) // 远小于 JSON
// 反序列化
var user2 pb.User
err = proto.Unmarshal(data, &user2)
if err != nil {
panic(err)
}
fmt.Println(user2.GetName()) // 布朗克
}
Protobuf 核心优势
- 📦 体积小:二进制编码,比 JSON 小 3-10 倍
- ⚡ 速度快:编解码效率是 JSON 的 5-10 倍
- 🔒 类型安全:强类型 schema,IDE 自动补全
- 🔄 向后兼容:字段编号机制保证版本兼容
- 🌐 跨语言:支持 C++、Java、Python、Go 等数十种语言
9. encoding/gob:Go 原生二进制编码
encoding/gob 是 Go 语言独有的二进制序列化方案,专为 Go 程序间的数据传输设计。
Gob 的特性
go
type GobMessage struct {
ID int
Content string
Values []float64
Created time.Time
}
func main() {
var buf bytes.Buffer
// 创建编码器和解码器
encoder := gob.NewEncoder(&buf)
decoder := gob.NewDecoder(&buf)
// 编码
msg := GobMessage{
ID: 42,
Content: "Hello Gob!",
Values: []float64{3.14, 2.718},
Created: time.Now(),
}
if err := encoder.Encode(msg); err != nil {
panic(err)
}
// 解码
var decoded GobMessage
if err := decoder.Decode(&decoded); err != nil {
panic(err)
}
fmt.Printf("%+v\n", decoded)
// {ID:42 Content:Hello Gob! Values:[3.14 2.718] Created:2026-06-17 ...}
}
Gob 的独特能力
1. 自动处理接口类型:Gob 可以编码和解码接口类型(前提是注册具体类型):
go
type Animal interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { return "汪汪" }
type Cat struct{ Name string }
func (c Cat) Speak() string { return "喵喵" }
func init() {
gob.Register(Dog{}) // 注册具体类型
gob.Register(Cat{})
}
2. 自动处理循环引用和共享对象:Gob 在编码流中维护对象引用表,不会重复编码相同的指针对象。
3. 不支持跨语言:Gob 是 Go-only 的方案,不适合与非 Go 服务通信。
10. 序列化方案性能对比
下表基于相同数据结构的序列化/反序列化基准测试:
| 方案 | 编码后大小 | 编码速度 | 解码速度 | 跨语言 | 人类可读 | Schema |
|---|---|---|---|---|---|---|
| JSON | 100% (基准) | 100% (基准) | 100% (基准) | ✅ | ✅ | ❌ |
| XML | ~150% | ~60% | ~50% | ✅ | ✅ | ❌ (DTD/XSD可选) |
| Protobuf | ~15-30% | ~200-500% | ~200-500% | ✅ | ❌ | ✅ |
| Gob | ~30-50% | ~150-200% | ~150-200% | ❌ | ❌ | ❌ |
选型建议
场景匹配矩阵:
📋 RESTful API / Web 前后端
→ JSON(主流、生态完善)
🏢 企业 SOAP / 政府系统对接
→ XML(历史遗留、必须兼容)
⚡ 微服务间高性能通信 / gRPC
→ Protobuf(速度快、体积小)
🔗 Go 服务间 RPC(无跨语言需求)
→ Gob(零配置、接口友好)
📂 配置文件 / 本地持久化
→ JSON 或 Viper(人类可编辑)
11. 小结与思考
本文系统梳理了 Go 语言中四种核心的序列化方案:
- 🥇 JSON:生态最完善,适合 Web API 和配置文件
- 🥈 Protobuf:性能最优,适合高性能微服务通信
- 🥉 Gob:Go 专属利器,接口类型支持独树一帜
- 🏅 XML:企业系统遗留兼容,按需选用
💬 互动思考
在实际项目中,你们团队主要使用哪种序列化方案?有没有遇到过 JSON 反序列化 interface{} 时数字变 float64 的坑?欢迎在评论区分享你的踩坑经历和最佳实践!
✍️ 作者:布朗克168
📚 系列目录:Go入门到精通2026
下一篇预告:《数据库操作》------database/sql、sqlx与GORM全攻略