Go 入门到精通-31-编码与序列化

目录

  • [🟠 Go 入门到精通:编码与序列化](#🟠 Go 入门到精通:编码与序列化)
    • [1. encoding/json:JSON 编解码核心](#1. encoding/json:JSON 编解码核心)
    • [2. 结构体 Tag 的艺术](#2. 结构体 Tag 的艺术)
    • [3. 自定义序列化行为](#3. 自定义序列化行为)
    • [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/jsonencoding/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全攻略

相关推荐
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai6 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜7 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa7 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio
一次旅行7 小时前
Python+大模型端到端自动化日报系统
开发语言·python·自动化
噢,我明白了8 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel
mabing9939 小时前
Qt 生成条纹图
开发语言·qt