系列:Go 语言从入门到进阶
作者:耿雨飞
适用版本:go v1.26.2
前置条件
在开始本章学习之前,请确保:
- 已完成第 10 章的学习,熟悉
io.Reader/io.Writer接口及其组合方式 - 理解结构体、接口、方法等 Go 核心概念
- 已获取 Go 1.26.2 源码树(
go-go1.26.2目录)
导读
序列化(Serialization)是将内存中的数据结构转换为可存储或传输的字节流的过程;反序列化(Deserialization)是其逆过程。Go 标准库提供了丰富的编码包,涵盖 JSON、XML、CSV、Gob、Binary 等格式,它们统一建立在io.Reader/io.Writer接口之上,与上一章学习的 I/O 体系无缝衔接。
本章将系统学习这些编码工具。我们从最常用的 JSON 处理出发,深入结构体标签机制和自定义编解码接口,然后扩展到 XML、CSV、Gob 和 binary 格式,最后学习强大的模板引擎。
本章将对照 Go 1.26.2 源码中的以下关键路径:
| 源码路径 | 内容说明 |
|---|---|
src/encoding/json/encode.go | JSON 编码核心:Marshal、Marshaler接口 |
src/encoding/json/decode.go | JSON 解码核心:Unmarshal、Unmarshaler接口 |
src/encoding/json/stream.go | 流式编解码:Encoder、Decoder |
src/encoding/json/tags.go | 结构体标签解析 |
src/encoding/xml/marshal.go | XML 编码与标签规则 |
src/encoding/xml/xml.go | XML 解析器 token 类型定义 |
src/encoding/csv/reader.go | CSV 读取器 |
src/encoding/csv/writer.go | CSV 写入器 |
src/encoding/gob/doc.go | Gob 格式说明 |
src/encoding/binary/binary.go | 底层二进制编解码 |
src/text/template/doc.go | 模板语法完整说明 |
src/html/template/doc.go | HTML 安全模板 |
学习目标
学完本章后,你应当能够:
- 使用
encoding/json完成 Go 值与 JSON 之间的编解码,理解类型映射规则 - 熟练运用结构体标签(
json:"field,omitempty")控制 JSON 字段行为 - 实现
MarshalJSON/UnmarshalJSON接口自定义复杂类型的编解码逻辑 - 使用
Encoder/Decoder进行流式 JSON 处理 - 掌握
encoding/xml的基本操作和标签规则 - 了解
encoding/csv、encoding/gob、encoding/binary的使用场景 - 使用
text/template和html/template进行数据驱动的文本生成
11.1 JSON 处理
JSON(JavaScript Object Notation)是当今最流行的数据交换格式。Go 的encoding/json包提供了完整的 JSON 支持,API 设计围绕两个核心函数——Marshal(编码)和Unmarshal(解码)。
11.1.1encoding/json:编码与解码
Marshal——Go 值到 JSON
Marshal将任意 Go 值递归编码为 JSON 字节切片:
// src/encoding/json/encode.go(第 205 行)funcMarshal(v any)([]byte,error){e:=newEncodeState()deferencodeStatePool.Put(e)err:=e.marshal(v,encOpts{escapeHTML:true})iferr!=nil{returnnil,err}buf:=append([]byte(nil),e.Bytes()...)returnbuf,nil}源码洞察:
Marshal使用sync.Pool(encodeStatePool)复用编码状态对象,避免频繁分配。编码完成后通过append([]byte(nil), ...)创建结果的独立副本,确保 Pool 回收不影响返回值。默认启用 HTML 转义(escapeHTML: true),会将<、>、&转义为\u003c、\u003e、\u0026。
Go 类型到 JSON 的映射规则:
| Go 类型 | JSON 类型 | 说明 |
|---|---|---|
bool | boolean | true/false |
int,float64等数值 | number | NaN/Inf 会返回错误 |
string | string | 自动转义非法 UTF-8 |
[]byte | string | base64 编码 |
struct | object | 导出字段成为 key |
map[string]T | object | key 必须是字符串或实现TextMarshaler |
[]T,[n]T | array | nil slice 编码为null |
*T(非 nil) | 递归 | 编码指针指向的值 |
*T(nil) | null | |
any(nil) | null |
基本使用示例:
packagemainimport("encoding/json""fmt")typeUserstruct{Namestring`json:"name"`Ageint`json:"age"`Emailstring`json:"email,omitempty"`}funcmain(){user:=User{Name:"Alice",Age:30}// 编码data,err:=json.Marshal(user)iferr!=nil{fmt.Println("编码错误:",err)return}fmt.Println(string(data))// {"name":"Alice","age":30}// 带缩进的编码pretty,_:=json.MarshalIndent(user,""," ")fmt.Println(string(pretty))// {// "name": "Alice",// "age": 30// }}Unmarshal——JSON 到 Go 值
Unmarshal解析 JSON 数据并存储到目标值中:
// src/encoding/json/decode.go(第 102 行)funcUnmarshal(data[]byte,v any)error{vard decodeState err:=checkValid(data,&d.scan)iferr!=nil{returnerr}d.init(data)returnd.unmarshal(v)}解码之前会先做完整的语法验证(checkValid),确保不会在解码到一半时才发现语法错误。
JSON 到 Go 的映射规则(解码到any接口时):
| JSON 类型 | Go 类型 |
|---|---|
| boolean | bool |
| number | float64 |
| string | string |
| array | []any |
| object | map[string]any |
| null | nil |
解码到结构体时,字段匹配规则:
- 优先匹配 JSON tag 指定的名称
- 其次匹配字段名(大小写不敏感)
- 未匹配的 JSON 字段被忽略(除非使用
Decoder.DisallowUnknownFields) - 多个字段匹配同一 key 时,精确匹配优先于大小写不敏感匹配
packagemainimport("encoding/json""fmt")funcmain(){jsonStr:=`{"name":"Bob","age":25,"email":"bob@example.com"}`varuser User err:=json.Unmarshal([]byte(jsonStr),&user)iferr!=nil{fmt.Println("解码错误:",err)return}fmt.Printf("%+v\n",user)// {Name:Bob Age:25 Email:bob@example.com}// 解码到 mapvarmmap[string]any json.Unmarshal([]byte(jsonStr),&m)fmt.Println(m["name"],m["age"])// Bob 25(age 是 float64)}流式编解码——Encoder 与 Decoder
当数据来源于io.Reader或需要写入io.Writer时,应使用流式 API:
// src/encoding/json/stream.go// Decoder 从 io.Reader 读取并解码 JSONtypeDecoderstruct{r io.Reader buf[]byted decodeState scanpint// 未读数据起始位置scannedint64// 已扫描的总字节数// ...}// Encoder 编码 JSON 并写入 io.WritertypeEncoderstruct{w io.Writer errerrorescapeHTMLbool// ...}Decoder的优势:
- 无需将整个 JSON 数据一次性加载到内存
- 支持
UseNumber()将数字解码为json.Number(字符串形式),避免精度丢失 - 支持
DisallowUnknownFields()拒绝结构体中没有对应字段的 JSON key
packagemainimport("encoding/json""fmt""os""strings")funcmain(){// Decoder:从 Reader 解码input:=`{"name":"Charlie","age":35}`+"\n"+`{"name":"Diana","age":28}`dec:=json.NewDecoder(strings.NewReader(input))fordec.More(){varuser Useriferr:=dec.Decode(&user);err!=nil{break}fmt.Printf("%+v\n",user)}// Encoder:编码到 Writerenc:=json.NewEncoder(os.Stdout)enc.SetIndent(""," ")enc.SetEscapeHTML(false)// 禁用 HTML 转义enc.Encode(User{Name:"Eve",Age:22})}Encoder.Encode会在每个 JSON 值后追加一个换行符(\n),这使得 NDJSON(Newline Delimited JSON)格式的数据可以自然地逐条写入和读取。
json.RawMessage——延迟解析
json.RawMessage类型是[]byte的别名,实现了Marshaler和Unmarshaler接口。它允许在解码时保留 JSON 原始内容,稍后再决定如何解析:
typeEventstruct{Typestring`json:"type"`Data json.RawMessage`json:"data"`// 延迟解析}funcmain(){raw:=`{"type":"user","data":{"name":"Frank","age":40}}`varevent Event json.Unmarshal([]byte(raw),&event)// 根据 type 决定如何解析 dataswitchevent.Type{case"user":varuser User json.Unmarshal(event.Data,&user)fmt.Printf("User: %+v\n",user)}}11.1.2 结构体标签(json:"field")
结构体标签是 Go 编码包的核心机制,它通过反射读取字段上的元数据来控制编解码行为。
标签解析逻辑在src/encoding/json/tags.go中:
// parseTag 将 tag 按第一个逗号拆分为名称和选项funcparseTag(tagstring)(string,tagOptions){tag,opt,_:=strings.Cut(tag,</