Go 语言中数据编码与解码
基本概念
Go 语言 encoding
包提供对多种网络流行的数据格式编码和解码功能,主要包括对 JSON、XML、CSV 等数据格式转换和 Base64 编码的操作。
对数据的转换和还原也称为序列化(Serialization)和反序列化(Deserialization):
- 序列化:序列化是指将数据转换成特定格式,同时保存好数据结构关键信息。
- 反序列化:反序列化指从序列化的格式中恢复数据结构或对象状态。
数据序列化操作的目的是方便数据储存和传输,例如通过 YAML 来保存服务配置信息,通过 JSON 返回服务器响应内容。为了直观表达下面用「生成」和「解析」指代。
Base64
Base64 编码在网络编程中很常见,用于将二进制数据转换为纯文本格式(ASCII 字符)。这样一来可以通过纯文本来传输二进制数据,二来可以规避传输原始数据中的非法字符。在 Go 语言中内置标准库 encoding/base64
支持 Base64 编码和解码。
编码原理
Base64 编码将每 3 个字节的二进制数据转换成 4 个字节的文本数据。下面以字符串 Mon
为例说明过程:
- 字符串
Mon
中 3 个字符 ASCII 码分别是[77 111 110]
。 - 将字符串转为字节切片表示,切片中每个字节占 8 位,以二进制形式表示为:
[01001101 01101111 01101110]
,共 24 位。 - Base64 每次处理 3 个字节,重新按照 6 位划分,变成 4 个字节:
[010011 010110 111101 101110]
,对应十进制值为:[19 22 61 46]
。由于 6 位只能表示数字0
到63
共 64 种(2^6
)可能值,故得名 Base64。 - 根据 Base64 索引表,这 4 个数字对应的字符是
TW9u
,也就是字符串Mon
对应的 Base64 编码。Base64 索引表包括大小写字母、数字、加号(+
)和斜杠(/
)。 - 如果最后一次处理不足 3 个字节,会用全 0 填充到 24 位再处理。结果末 2 位字节为
00000000
时,用等号(=
)填充。 - 由于编码后数据以字符形式存储,相当于每转 3 个字节,就多出来 1 个字节,编码后体积比原先大 1/3。同理,Base32 编码后数据大小增加 3/5。
下面用简单直白的代码模拟这一过程,特意在原字符串前后加入控制字符,以观察填充处理:
package main import ( "encoding/base64" "fmt" "strconv" ) func main() { // 定义数据,转为字节切片 a := "\u0003Mon\u0003" b := []byte(a) fmt.Println(b) // 输出:[3 77 111 110 3],对应字符 ASCII 编码 // 以 3 字节为倍数检查,补全位数 if len(b)%3 != 0 { c := make([]byte, 3-len(b)%3) b = append(b, c...) } fmt.Println(b) // 输出:[3 77 111 110 3 0] // 转为二进制字符串并连接 d := "" for _, b := range b { d += fmt.Sprintf("%08b", b) } fmt.Println(d) // 输出:000000110100110101101111011011100000001100000000 // 分割为 6 位一组 e := make([]string, 0) for i := 0; i < len(d); i += 6 { e = append(e, d[i:i+6]) } fmt.Println(e) // 输出:[000000 110100 110101 101111 011011 100000 001100 000000] // 转为整型切片 f := make([]int, len(e)) for i, v := range e { n, _ := strconv.ParseInt(v, 2, 64) f[i] = int(n) } fmt.Println(f) // 输出:[0 52 53 47 27 32 12 0] // 生成 BASE64 编码映射 g := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" h := make(map[int]string) for i, v := range g { h[i] = string(v) } fmt.Println(h) // 对应到 BASE64 编码,输出最终结果 j := "" for i, v := range f { vv := h[v] // 特殊规则,用 = 代替空位 if i > len(f)-3 && len(f)>3 && vv == "A" { vv = "=" } j += vv } fmt.Println(j) // 输出:A01vbgM= }
解码原理
解码则是编码逆过程,将 4 个字符转为 3 个字节,还原出原始字符串。转换时遇到填充字符 =
则忽略:
package main import ( "encoding/base64" "fmt" "strconv" ) func main() { // 定义编码数据和索引表 a := "A01vbgM=" b := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" c := make(map[string]int) for i, v := range b { c[string(v)] = i } fmt.Println(c) // 转为整型切片 d := make([]int, 0) for _, v := range a { d = append(d, c[string(v)]) } fmt.Println(d) // 转为二进制字符串 e := "" for _, v := range d { e += fmt.Sprintf("%06b", v) } fmt.Println(e) // 分割为 8 位一组 f := make([]string, 0) for i := 0; i < len(e); i += 8 { // 特殊规则,最后两位空位时忽略 if i > len(e)-24 && len(e)>24 && e[i:i+8] == "00000000" { continue } f = append(f, e[i:i+8]) } fmt.Println(f) // 转为字节切片,打印结果 g := make([]byte, len(f)) for i, v := range f { b, _ := strconv.ParseUint(v, 2, 8) g[i] = byte(b) } fmt.Println(g) // 输出:[3 77 111 110 3] fmt.Println(string(g)) }
标准编码
标准编码指使用标准 Base64 字符集进行编解码。在 Go 语言中,标准编码功能已经封装到 base64.StdEncoding
包中:
package main import ( "encoding/base64" "fmt" ) func main() { // 标准编码 fmt.Println(base64.StdEncoding.EncodeToString([]byte("a"))) // YQ== // 标准解码 fmt.Println(base64.StdEncoding.DecodeString("YQ==")) // 输出:[97] <nil> // 实际应用中需要处理解码错误 d, err := base64.StdEncoding.DecodeString("YQ=") if err != nil { fmt.Println("解码出错:", err) // 报错:illegal base64 data at input byte 3 return } fmt.Println(string(d)) }
安全编码
标准 Base64 字符集包含符号 +
和 /
,在网络传输时不能保证安全。可使用 URL 安全编码方式 base64.URLEncoding
,编码后 +
和 /
字符替换为 -
和 _
字符:
package main import ( "encoding/base64" "fmt" ) func main() { // 在编码结果中产生 + 和 / 字符 data := []byte{0xfb, 0xff, 0xbf} // 标准 Base64 编码,原样输出 fmt.Println(base64.StdEncoding.EncodeToString(data)) // 输出:+/+/ // 安全编码 fmt.Println(base64.URLEncoding.EncodeToString(data)) // 输出:-_-_ // 解码时也要使用对应方法 fmt.Println(base64.URLEncoding.DecodeString("-_-_")) // 输出:[251 255 191] <nil> }
JSON
JSON(JavaScript Object Notation)全称 JavaScript 对象表示法,是互联网上最流行的轻量级数据交换格式。在 Go 语言中,内置 encoding/json
库提供对 JSON 格式支持。
基本结构
JSON 使用文本格式保存(文件后缀名 .json
),可以支持键值对集合和数组结构:
- 对象(Object): 由花括号
{}
包围的一组无序键值对。键为字符串,值为任意类型。键和值之间用冒号分隔,键值对之间用逗号分隔。 - 数组(Array): 由方括号
[]
包围的有序值列表。值可以为任意类型。多个值之间用逗号分隔。 - 值(Value): 可以是字符串、数字、布尔值、数组、对象或者空值。
JSON 文档只能有一个顶级值,也就是说一个文档等于一个对象、数组或值,不能有多个并列顶级值。例如用对象保存用户信息:
{ "name": "张三", // 字符串,用双引号包围 "age": 28, // 数值,整数或浮点数 "isStudent": false, // 布尔值,true 或 false "skills": ["Java", "Python", 100], // 数组,可以包含任意类型 "address": { // 对象,包含任意数量键值对 "street": null, // 空值 "city": "北京", "money": 300, "country": "中国" // 注意最后一个键值对后不能有逗号 } }
在 Go 语言中,通过结构体标签来保存 JSON 字段名等信息。JSON 与 Go 语言类型默认对应关系如下:
类型 | JSON | Go |
---|---|---|
字符串 | String |
string |
数字 | Number |
float64 |
布尔值 | Boolean |
bool |
数组 | Array |
[]interface{} |
对象 | Object |
map[string]interface{} |
空值 | Null |
nil |
当然也可以自定义类型对应关系,例如把字符串格式的时间映射到 time.Time
类型,只要类型能匹配上:
package main import ( "encoding/json" "fmt" "time" ) // Event 结构体包含 time.Time 类型时间戳字段 type Event struct { Name string `json:"name"` Timestamp time.Time `json:"timestamp"` } func main() { // JSON 字符串包含标准格式的日期时间字符串 j := `{ "name": "Webinar", "timestamp": "2020-05-01T14:53:00Z" }` // 输出解析结果 var event Event json.Unmarshal([]byte(j), &event) fmt.Println(event.Name, event.Timestamp) // 输出:Webinar 2020-05-01 14:53:00 +0000 UTC }
如果使用 GoLand
编辑器,向编辑区粘贴 JSON 格式内容,会提示生成对应结构体定义。
生成
序列化函数有 Marshal
和 MarshalIndent
,后者能生成美化后的格式:
package main import ( "encoding/json" "fmt" "os" ) // 先定义结构体类型,包含 JSON 标签 type User struct { Name string `json:"name"` } func main() { // 转换结构体 data, err := json.Marshal(User{"张三"}) if err != nil { fmt.Println(err) return } fmt.Println(string(data)) // 输出:{"name":"张三"} // 转换映射,键必须为字符串 data, _ = json.Marshal(map[string]string{"name": "李四"}) fmt.Println(string(data)) // 输出:{"name":"李四"} // 转换结构体数组,使用 MarshalIndent 让最终结果好看一点 data, _ = json.MarshalIndent([]User{{"张三"}, {"李四"}}, "", " ") fmt.Println(string(data)) // 输出:[{"name":"张三"},{"name":"李四"}] // 结果为字节切片,可以直接写入到文件 file, _ := os.Create("data.json") file.Write(data) file.Close() }
虽然可以将基础类型数据转为 JSON 格式,但是没有实用价值。此外,转换 JSON 格式不支持的数据类型(例如接口和通道)会报错。
解析
反序列化使用对应函数 Unmarshal
。一般先定义结构体,通过标签将字段名对应到 JSON 键名,然后传入结构体实例指针到函数,接收转换结果:
package main import ( "encoding/json" "fmt" "os" ) // 定义一个结构体,Port 可以是 int 也可以是 string type Server struct { Host string `json:"host"` Port any `json:"port"` } func main() { // JSON 字符串 jsonData := ` [ { "name": "backend", "host": "127.0.0.1", "port": 1080 }, { "name": "frontend", "host": "127.0.0.2", "port": "80" } ]` // 解析 JSON 到结构体切片,其中 name 字段不需要,直接忽略 var servers []Server err := json.Unmarshal([]byte(jsonData), &servers) // 输出目标必须为实例指针 if err != nil { fmt.Println("Error parsing JSON:", err) } // 打印结果,展示 port 字段类型多样性 for _, server := range servers { fmt.Printf("Host: %s, Port: %v (%T)\n", server.Host, server.Port, server.Port) } // 从文件读取 JSON 内容不需要转为字节切片,直接使用 bytes, _ := os.ReadFile("data.json") json.Unmarshal(bytes, &servers) // 存到同一个目标对象,会覆盖原数据 fmt.Printf("Verified: %+#v\n", servers) }
当 JSON 数据是从一个流(如文件或网络)中获取时,可以直接使用 NewDecoder
配合 Decoder
函数来解码:
package main import ( "encoding/json" "fmt" "net/http" "os" ) // User 结构体只取两个字段 type User struct { Login string `json:"login"` Id int `json:"id"` } func main() { // 从网络获取数据 var user User r, _ := http.Get("https://api.github.com/users/hxz393") defer r.Body.Close() json.NewDecoder(r.Body).Decode(&user) // 直接解码响应体后赋值给 user fmt.Printf("%+#v\n", user) // 输出:main.User{Login:"hxz393", Id:5063578} // 从文件获取数据 var users []User file, _ := os.Open("data.json") defer file.Close() json.NewDecoder(file).Decode(&users) // 同样用法 fmt.Printf("%+#v\n", users) // 输出:[]main.User{main.User{Login:"张三", Id:0}, main.User{Login:"李四", Id:0}} }
标签选项
在序列化和反序列化时,可以通过标签选项来控制字段转换行为:
-
:当标签值为-
时,无论是序列化还是反序列化,该字段都会被忽略。omitempty
:在序列化时,如果字段值是类型零值,则不生成到 JSON 数据中。string
:在序列化时,将字段值转为 JSON 中的字符串。
此外要注意大小写规则:
- 使用
json.Unmarshal
反序列化时,标签值匹配不区分字段大小写,例如json:"login"
可以匹配到 JSON 数据中Login
、login
或LOGIN
等字段。 - 非导出字段数据序列化时,不会生成到 JSON 数据中,也就是小写字母开头的字段,转换时会被忽略。而非导出字段也不能储存反序列化结果,会导致报错。
下面是演示代码:
package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"Name"` // 必须处理字段,故意使用首字母大写 Age int `json:"age,omitempty,string"` // 如果为 0,则在序列化时忽略 City string `json:"-"` // 强制忽略字段 id int `json:"id"` // 不能转换字段 } func main() { // 反序列化。标签中的 Name 匹配到了 JSON 中 name 字段。city 字段值被忽略 jsonStr := `{"name":"Alice", "age":30, "city":"New York"}` var p Person json.Unmarshal([]byte(jsonStr), &p) fmt.Printf("%+v\n", p) // 输出:{Name:Alice Age:0 City: id:0} // 序列化。Age 和 City 字段被忽略,不会出现在结果 JSON 中 p = Person{Name: "", Age: 0, City: "Los Angeles", id: 011} jsonData, _ := json.Marshal(p) fmt.Println(string(jsonData)) // 输出:{"Name":""} }
第三方库
内置库使用反射来实现解析,可以试试不用反射的第三方包 github.com/valyala/fastjson
,性能更好:
package main import ( "encoding/json" "testing" "github.com/valyala/fastjson" ) // 测试结构体 type Person struct { Name string `json:"name"` Age int `json:"age"` Country string `json:"country"` } // 测试数据 var jsonData = `{"name":"John Doe","age":30,"country":"USA"}` // 使用 encoding/json 解析 func BenchmarkEncodingJSON(b *testing.B) { var p Person for i := 0; i < b.N; i++ { if err := json.Unmarshal([]byte(jsonData), &p); err != nil { b.Fatal(err) } } } // 使用 fastjson 解析 func BenchmarkFastJSON(b *testing.B) { var p fastjson.Parser for i := 0; i < b.N; i++ { v, err := p.Parse(jsonData) if err != nil { b.Fatal(err) } _ = v.GetStringBytes("name") _ = v.GetInt("age") _ = v.GetStringBytes("country") } }
基准测试结果如下:
goos: windows goarch: amd64 pkg: new cpu: AMD Ryzen Threadripper 2990WX 32-Core Processor BenchmarkEncodingJSON BenchmarkEncodingJSON-64 639985 1677 ns/op BenchmarkFastJSON BenchmarkFastJSON-64 5361942 226.0 ns/op PASS
XML
XML(Extensible Markup Language)叫可扩展标记语言,是一种基于文本的结构化标记语言,过去广泛用于 Windows 平台应用配置。Go 语言内置 encoding/xml
库提供支持。
基本结构
XML 和 HTML 语言很相似,但 XML 允许用户自定义元素标签和文档结构:
- 声明(Prolog):在文档最开头声明 XML 版本和文件编码。例如:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
。 - 标签(Tags):XML 数据用标签包围,所有元素都必须有闭合标签或自闭合标签。例如:
<Tracker>Peer Exchange</Tracker>
。 - 属性(Attributes):标签内可以包含属性,用来提供更多关于 XML 元素的信息。例如:
<BitFieldStatus TotalLength="75312677588" PieceLength="16777216">
。 - 嵌套:标签内可以嵌套其他标签数据。例如:
<TrackerList><Tracker>Peer Exchange</Tracker></TrackerList>
。
和 JSON 不同,XML 中数据均以字符串形式保存。
生成
序列化时通过标签选项 attr
来设置标签属性:
package main import ( "encoding/xml" "os" ) type Person struct { Name string `xml:"name"` Age int `xml:"age,attr"` // 序列化为 Person 标签属性 City string `xml:"city"` } func main() { p := Person{Name: "Aku", Age: 30, City: "New York"} // 带缩进格式序列化,指定前缀为空,省略错误处理 output, _ := xml.MarshalIndent(p, "", "\t") os.Stdout.Write(output) }
解析
反序列化是将 XML 数据转回 Go 数据结构,XML 文档中第一行声明会被自动忽略,不用特殊处理:
package main import ( "encoding/xml" "fmt" "os" ) type Person struct { XMLName xml.Name `xml:"person"` Name string `xml:"name"` Age int `xml:"age"` } func main() { // 支持从文件直接读取内容 data, _ := os.ReadFile("data.xml") data = []byte(`<person><name>Aku</name><age>30</age></person>`) // 输出结果:main.Person{XMLName:xml.Name{Space:"", Local:"person"}, Name:"Aku", Age:30} var p Person xml.Unmarshal(data, &p) fmt.Printf("%+#v", p) }
CSV
CSV(Comma-Separated Values)全名叫逗号分隔值,是一种用于存储表格数据的文件格式,常见于数据库和表格数据导入、导出和处理。Go 语言由内置库 encoding/csv
提供支持。
基本格式
CSV 文件数据结构是表格式的,有表头、行和列概念:
- 行:每行对应一条数据记录。第一行可以是标题行(表头),记录每个字段名。
- 分隔符:每条记录中,字段值之间用分隔符隔开。默认是逗号,也可以自定义分隔符,常用制表符和分号。
- 字段:字段使用双引号包围,支持转义字符。每条记录字段值数量必须一致,否则会解析出错。
CSV 采用纯文本格式储存数据,所有数据都是字符串类型。
解析
由于 CSV 是扁平数据结构,没有层级或嵌套关系,因此处理 CSV 文件和处理文本文件一样,可以直接读取得到二维切片:
package main import ( "encoding/csv" "fmt" "io" "os" ) func main() { // 打开 CSV 文件 file, _ := os.Open("data.csv") defer file.Close() // 创建 CSV 读取器 reader := csv.NewReader(file) reader.Comma = ',' // 如果不是逗号作为分隔符,需要单独配置 // 读取所有数据到二维切片 data, _ := reader.ReadAll() fmt.Printf("%+#v", data) // 逐行读取方式 for { record, err := reader.Read() if err != nil { if err == io.EOF { // 文件读取完成 break } } fmt.Println(record) } // 带表头行,可以用映射切片来保存 headers := data[0] var records []map[string]string for _, row := range data[1:] { // 跳过表头行 record := make(map[string]string) for i, value := range row { record[headers[i]] = value } records = append(records, record) } fmt.Printf("%+#v", records) for _, record := range records { fmt.Println(record) } }
大型 CSV 文件应使用 Read
循环逐行读取,以避免内存溢出。
生成
生成 CSV 文件用到 csv.NewWriter
函数,并通过 writer.Comma
来设置分隔符:
package main import ( "encoding/csv" "os" ) func main() { // 原数据格式需要是二维切片 records := [][]string{ {"Name", "City", "Age"}, {"Alice", "New York", "30"}, {"Bob", "Los Angeles", "25"}, } file, _ := os.Create("data.csv") defer file.Close() writer := csv.NewWriter(file) writer.Comma = ',' // 可自定义分隔符为别的符号 defer writer.Flush() // 循环写入到文件 for _, record := range records { if err := writer.Write(record); err != nil { panic(err) } } }
YAML
YAML(YAML Ain’t Markup Language)格式储存数据方式类似 JSON,只是在书写格式上采用 Python 式缩进。Go 语言中需要用第三方库来处理 YAML,常用的是 gopkg.in/yaml/v3
。
基本语法
YAML 中用标量指代基本数据类型,如字符串、整数和浮点数:
- 标量:单个不可分割的值。可以是单行或多行文本。
- 列表:一系列有序排列值。列表元素前使用短横线
-
标记。 - 映射:键值对集合。使用冒号
:
分隔键和值。 - 注释:支持单行注释。注释行以井号
#
开始。 - 多文档:一个文件可以包含多个文档。使用
---
分隔符分隔多个文档。
下面是一个标准 YAML 文件内容:
name: Alice skills: - Python - Golang --- spring: application: name: order servlet: multipart: max-file-size: 50MB max-request-size: 100MB mvc: async: request-timeout: 300000 sleuth: enabled: true server: tomcat: relaxed-query-chars: "[,]"
注意每个层次之间有两个空格缩进,空格数不正确会导致解析失败。
解析
YAML 反序列化时,甚至不需要依赖结构体标签,第三方库做了非常多适配处理:
package main import ( "fmt" "os" "gopkg.in/yaml.v3" // 前面加空行来分组 ) // Config 结构体只取 spring 段 type Config struct { Spring struct { Application struct { Name string `yaml:"name"` } `yaml:"application"` // 结构体名相同时(不分大小写),标签可省 Servlet struct { Multipart struct { MaxSizeBytes string `yaml:"max-file-size"` MaxRequestSize string `yaml:"max-request-size"` } } } } // YAML 格式原生字符串 var data = ` spring: application: name: order servlet: multipart: max-file-size: 50MB max-request-size: 100MB server: tomcat: relaxed-query-chars: "[,]" ` func main() { // 解析 YAML 到结构体 var config Config yaml.Unmarshal([]byte(data), &config) fmt.Printf("%+v\n", config) // 从文件读取,直接使用流式处理 var configGo Config file, _ := os.Open("data.yaml") defer file.Close() yaml.NewDecoder(file).Decode(&configGo) fmt.Printf("%+v\n", configGo) }
生成
序列化时,如果结构体没有标签,YAML 键名沿用小写结构体字段名。默认情况下生成的 YAML 就是标准格式,没有也不需要 MarshalIndent
函数,但可自定义层级缩进量:
package main import ( "fmt" "gopkg.in/yaml.v3" "os" ) type Config struct { Spring struct { Application struct { Name string `yaml:"name"` } Servlet struct { Multipart struct { MaxSizeBytes string // 没有标签,自动生成键 maxsizebytes MaxRequestSize []int `yaml:"max-request-size"` } } } } func main() { // 正常结构体示例,保存配置信息 var config Config config.Spring.Application.Name = "pay" config.Spring.Servlet.Multipart.MaxRequestSize = []int{50, 100} // 从结构体生成 YAML 格式,默认缩进 4 个空格 data, _ := yaml.Marshal(&config) fmt.Printf("%s\n", data) // 使用流式处理写入到文件 file, _ := os.Create("config.yaml") defer file.Close() encoder := yaml.NewEncoder(file) encoder.SetIndent(2) // 调整设置缩进为 2 个空格 encoder.Encode(config) }
TOML
TOML(Tom’s Obvious, Minimal Language)是一种新近的格式,在 Rust 语言中作为配置用得比较多。TOML 在语法上类似传统的 INI 配置文件,但是支持更多数据类型。在 Go 语言中处理 TOML 文件常用 github.com/BurntSushi/toml
库和 github.com/pelletier/go-toml/v2
库。
基本语法
这里不详细说明 TOML 格式语句,仅简单介绍:
- 键值对:内容最基本组成部分,用等号
=
分隔键和值。 - 值类型:基本类型有字符串(支持多行和原生字符串)、数值、布尔值和时间(ISO 8601 格式)。此外还支持数组,数组用方括号
[]
括起相同基本类型元素,元素间用逗号,
分隔。 - 表:使用方括号
[]
括起表名,表示表的开始,支持嵌套。还有一种用双方括号[[]]
括起来的数组表。 - 注释:支持用井号
#
开头的注释。 - 点分键:用于在一个表中定义层嵌套表结构。
- 内联表:使用花括号
{}
。例如下面来自官网的示例:
title = "TOML Example" [owner] name = "Tom Preston-Werner" dob = 1979-05-27T07:32:00Z类型 [database] server = "192.168.1.1" ports = [8001, 8001, 8002] connection_max = 5000 enabled = true
解析
TOML 格式反序列化没有特别之处,库 github.com/BurntSushi/toml
提供从文件直接解析功能:
package main import ( "fmt" "os" "time" tomlB "github.com/BurntSushi/toml" "github.com/pelletier/go-toml/v2" ) // 内嵌结构体最好单独定义,不要使用匿名嵌套 type Config struct { Title string Owner OwnerInfo Database DatabaseInfo } type OwnerInfo struct { Name string Dob time.Time } type DatabaseInfo struct { Server string Ports []int ConnectionMax int `toml:"connection_max"` Enabled bool } func main() { var config, configB Config // 先读取文件内容再解析 data, _ := os.ReadFile("config.toml") toml.Unmarshal(data, &config) // 直接从文件解析 tomlB.DecodeFile("config.toml", &configB) // 输出结果一样 fmt.Printf("%+v\n", config) fmt.Printf("%+v\n", configB) }
生成
这里用一个最简配置来演示序列化:
package main import ( "os" tomlB "github.com/BurntSushi/toml" "github.com/pelletier/go-toml/v2" ) func main() { config := struct{ Title string }{"TOML Example"} // 序列化后写入 data, _ := toml.Marshal(config) os.WriteFile("data.toml", data, 0644) // 调用 NewEncoder 方法写入,实际上两个库用法一样 file, _ := os.Create("data.toml") tomlB.NewEncoder(file).Encode(config) }