系列: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.goJSON 编码核心:Marshal、Marshaler接口src/encoding/json/decode.goJSON 解码核心:Unmarshal、Unmarshaler接口src/encoding/json/stream.go流式编解码:Encoder、Decodersrc/encoding/json/tags.go结构体标签解析src/encoding/xml/marshal.goXML 编码与标签规则src/encoding/xml/xml.goXML 解析器 token 类型定义src/encoding/csv/reader.goCSV 读取器src/encoding/csv/writer.goCSV 写入器src/encoding/gob/doc.goGob 格式说明src/encoding/binary/binary.go底层二进制编解码src/text/template/doc.go模板语法完整说明src/html/template/doc.goHTML 安全模板学习目标学完本章后,你应当能够:使用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 值到 JSONMarshal将任意 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 类型说明boolbooleantrue/falseint,float64等数值numberNaN/Inf 会返回错误stringstring自动转义非法 UTF-8[]bytestringbase64 编码structobject导出字段成为 keymap[string]Tobjectkey 必须是字符串或实现TextMarshaler[]T,[n]Tarraynil slice 编码为null*T(非 nil)递归编码指针指向的值*T(nil)nullany(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 类型booleanboolnumberfloat64stringstringarray[]anyobjectmap[string]anynullnil解码到结构体时,字段匹配规则:优先匹配 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 keypackagemainimport("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,/