JSON 处理:Go 与数据交换的桥梁
如果你在做 Web 开发,那你几乎每天都会和 JSON 打交道。JSON(JavaScript Object Notation)已经成为当今互联网最流行的数据交换格式——API 返回 JSON,配置文件用 JSON,消息队列里传输的也是 JSON。
Go 语言的 encoding/json 包提供了强大而优雅的 JSON 处理能力。它最大的特色是利用结构体标签(struct tags)来控制序列化和反序列化的行为,让你用很少的代码就能完成复杂的数据转换。
今天我们就来全面学习 Go 的 JSON 处理,从基础到高级,让你成为 JSON 大师。
基础:序列化和反序列化
序列化:结构体 → JSON
把 Go 的数据结构转换成 JSON 字符串叫做序列化(marshaling)。
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string
Age int
Email string
}
func main() {
user := User{
Name: "张三",
Age: 25,
Email: "zhangsan@example.com",
}
// Marshal:结构体 → JSON 字节切片
data, err := json.Marshal(user)
if err != nil {
fmt.Println("序列化失败:", err)
return
}
fmt.Println(string(data))
// {"Name":"张三","Age":25,"Email":"zhangsan@example.com"}
// MarshalIndent:格式化的 JSON(带缩进)
pretty, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(pretty))
}
输出:
{"Name":"张三","Age":25,"Email":"zhangsan@example.com"}
{
"Name": "张三",
"Age": 25,
"Email": "zhangsan@example.com"
}
MarshalIndent 的第二个参数是前缀,第三个参数是缩进字符。通常用于生成可读的 JSON 文件。
反序列化:JSON → 结构体
把 JSON 字符串转换回 Go 的数据结构叫做反序列化(unmarshaling)。
jsonData := `{"Name":"李四","Age":30,"Email":"lisi@example.com"}`
var user User
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("反序列化失败:", err)
return
}
fmt.Printf("%+v\n", user)
// {Name:李四 Age:30 Email:lisi@example.com}
⚠️ 注意:Unmarshal 的第一个参数是 []byte,不是 string。如果你的 JSON 是字符串,需要先转换。
结构体标签(Struct Tags)
默认情况下,Go 用字段名作为 JSON 的键。但 JSON 通常使用小写或驼峰命名,而 Go 字段需要大写才能导出。这就是结构体标签的用武之地。
基本标签
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
CreatedAt string `json:"created_at"`
}
现在序列化时,JSON 键就是小写的了:
user := User{
Name: "张三",
Age: 25,
Email: "zhangsan@example.com",
}
data, _ := json.Marshal(user)
fmt.Println(string(data))
// {"name":"张三","age":25,"email":"zhangsan@example.com","created_at":""}
常用标签选项
omitempty:零值字段不序列化
type User struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
Email string `json:"email,omitempty"`
Address string `json:"address,omitempty"`
}
user := User{Name: "张三", Age: 25}
data, _ := json.Marshal(user)
fmt.Println(string(data))
// {"name":"张三","age":25}
// 注意:email 和 address 因为是零值,被省略了
-:完全忽略字段
type User struct {
Name string `json:"name"`
Password string `json:"-"` // 永远不序列化
Age int `json:"age"`
Internal string `json:"-,omitempty"` // 字段名就叫 "-"
}
⚠️ 注意:json:"-" 和 json:"-,omitempty" 是不同的。前者是完全忽略,后者是字段名为 -。
多个标签共存
一个字段可以有多个标签(json、xml、yaml 等),用空格分隔:
type User struct {
Name string `json:"name" xml:"Name" yaml:"name"`
Email string `json:"email,omitempty" xml:"Email,omitempty"`
}
处理嵌套结构
JSON 可以嵌套,Go 结构体也可以嵌套:
type Address struct {
City string `json:"city"`
Province string `json:"province"`
}
type User struct {
Name string `json:"name"`
Address Address `json:"address"`
}
func main() {
user := User{
Name: "张三",
Address: Address{
City: "北京",
Province: "北京市",
},
}
data, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(data))
}
输出:
{
"name": "张三",
"address": {
"city": "北京",
"province": "北京市"
}
}
嵌套数组
type User struct {
Name string `json:"name"`
Tags []string `json:"tags"`
Scores []int `json:"scores"`
}
user := User{
Name: "张三",
Tags: []string{"developer", "gopher"},
Scores: []int{95, 88, 92},
}
data, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(data))
处理未知结构的 JSON
有时候你不知道 JSON 的完整结构,或者只想提取其中一部分数据。这时候可以用 map[string]interface{}:
jsonData := `{
"name": "张三",
"age": 25,
"skills": ["Go", "Python", "JavaScript"],
"address": {
"city": "北京",
"province": "北京市"
}
}`
var data map[string]interface{}
json.Unmarshal([]byte(jsonData), &data)
fmt.Println("姓名:", data["name"])
fmt.Println("年龄:", data["age"])
skills := data["skills"].([]interface{})
for _, skill := range skills {
fmt.Println("技能:", skill)
}
address := data["address"].(map[string]interface{})
fmt.Println("城市:", address["city"])
⚠️ 注意:JSON 数字反序列化到 interface{} 时,会变成 float64 类型。如果你需要整数,要做类型转换:
age := int(data["age"].(float64))
json.RawMessage:延迟解析
如果你只想解析 JSON 的一部分,可以用 json.RawMessage 保存原始 JSON 数据:
type Response struct {
Status string `json:"status"`
Message string `json:"message"`
Data json.RawMessage `json:"data"` // 暂时不解析
}
func main() {
jsonStr := `{
"status": "ok",
"message": "success",
"data": {"user": "张三", "age": 25}
}`
var resp Response
json.Unmarshal([]byte(jsonStr), &resp)
fmt.Println("状态:", resp.Status)
// 稍后再根据情况解析 Data
var userData map[string]interface{}
json.Unmarshal(resp.Data, &userData)
fmt.Println("用户:", userData)
}
自定义序列化和反序列化
有时候默认的序列化行为不够灵活,你可以实现 Marshaler 和 Unmarshaler 接口来自定义。
自定义 MarshalJSON
package main
import (
"encoding/json"
"fmt"
"time"
)
type Event struct {
Name string `json:"name"`
Timestamp time.Time `json:"timestamp"`
}
// MarshalJSON 自定义序列化
func (e Event) MarshalJSON() ([]byte, error) {
type Alias Event
return json.Marshal(&struct {
Timestamp string `json:"timestamp"`
*Alias
}{
Timestamp: e.Timestamp.Format("2006-01-02 15:04:05"),
Alias: (*Alias)(&e),
})
}
func main() {
event := Event{
Name: "会议",
Timestamp: time.Now(),
}
data, _ := json.MarshalIndent(event, "", " ")
fmt.Println(string(data))
}
自定义 UnmarshalJSON
// UnmarshalJSON 自定义反序列化
func (e *Event) UnmarshalJSON(data []byte) error {
type Alias Event
aux := &struct {
Timestamp string `json:"timestamp"`
*Alias
}{
Alias: (*Alias)(e),
}
if err := json.Unmarshal(data, aux); err != nil {
return err
}
t, err := time.Parse("2006-01-02 15:04:05", aux.Timestamp)
if err != nil {
return err
}
e.Timestamp = t
return nil
}
💡 小贴士:注意 type Alias Event 的用法——它创建一个和 Event 结构相同但没有自定义方法的别名,避免递归调用 MarshalJSON。
流式处理
对于大文件或者持续输出的 JSON 数据,用流式处理更合适。
解码器(Decoder)
package main
import (
"encoding/json"
"fmt"
"strings"
)
func main() {
jsonStream := `
{"name": "张三", "age": 25}
{"name": "李四", "age": 30}
{"name": "王五", "age": 28}
`
decoder := json.NewDecoder(strings.NewReader(jsonStream))
for decoder.More() {
var user struct {
Name string `json:"name"`
Age int `json:"age"`
}
if err := decoder.Decode(&user); err != nil {
fmt.Println("解码失败:", err)
break
}
fmt.Printf("用户: %s, 年龄: %d\n", user.Name, user.Age)
}
}
编码器(Encoder)
package main
import (
"encoding/json"
"os"
)
func main() {
file, _ := os.Create("output.json")
defer file.Close()
encoder := json.NewEncoder(file)
users := []struct {
Name string `json:"name"`
Age int `json:"age"`
}{
{"张三", 25},
{"李四", 30},
{"王五", 28},
}
for _, user := range users {
if err := encoder.Encode(user); err != nil {
fmt.Println("编码失败:", err)
}
}
}
处理 JSON 数组
// 解码数组
jsonStr := `[{"name":"张三","age":25},{"name":"李四","age":30}]`
var users []struct {
Name string `json:"name"`
Age int `json:"age"`
}
json.Unmarshal([]byte(jsonStr), &users)
for _, u := range users {
fmt.Printf("%s: %d\n", u.Name, u.Age)
}
// 编码数组
data, _ := json.Marshal(users)
fmt.Println(string(data))
实战:JSON 配置管理
让我们写一个配置管理工具:
package main
import (
"encoding/json"
"fmt"
"os"
)
type Config struct {
AppName string `json:"app_name"`
Version string `json:"version"`
Debug bool `json:"debug"`
Port int `json:"port"`
Database DatabaseConfig `json:"database"`
Features []string `json:"features"`
Options map[string]string `json:"options,omitempty"`
}
type DatabaseConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"-"` // 密码不序列化
Database string `json:"database"`
}
func (c *Config) Save(path string) error {
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
func main() {
config := &Config{
AppName: "MyApp",
Version: "1.0.0",
Debug: true,
Port: 8080,
Database: DatabaseConfig{
Host: "localhost",
Port: 5432,
Username: "admin",
Password: "secret123",
Database: "mydb",
},
Features: []string{"logging", "metrics", "tracing"},
Options: map[string]string{
"log_level": "debug",
"timeout": "30s",
},
}
// 保存配置
err := config.Save("config.json")
if err != nil {
fmt.Println("保存失败:", err)
return
}
fmt.Println("✅ 配置已保存到 config.json")
// 加载配置
loaded, err := LoadConfig("config.json")
if err != nil {
fmt.Println("加载失败:", err)
return
}
fmt.Printf("✅ 配置已加载: %s v%s\n", loaded.AppName, loaded.Version)
fmt.Printf(" 数据库: %s:%d/%s\n",
loaded.Database.Host,
loaded.Database.Port,
loaded.Database.Database)
}
常见陷阱
1. 未导出字段不会被序列化
type User struct {
Name string // 会序列化
age int // ❌ 不会序列化(未导出)
Email string // 会序列化
}
2. 数字类型转换问题
JSON 数字反序列化到 interface{} 时默认是 float64:
var data map[string]interface{}
json.Unmarshal([]byte(`{"age": 25}`), &data)
// ❌ 直接转 int 会 panic
// age := data["age"].(int)
// ✅ 正确做法
age := int(data["age"].(float64))
3. 时间格式问题
默认的 time.Time 序列化使用 RFC3339 格式:
type Event struct {
Time time.Time `json:"time"`
}
// {"time":"2021-02-16T13:10:00+08:00"}
如果你需要自定义格式,要实现 MarshalJSON 和 UnmarshalJSON。
4. 循环引用
结构体有循环引用时,序列化会导致栈溢出:
type Node struct {
Value int
Next *Node
}
// 如果有循环,Marshal 会无限递归
第三方 JSON 库
虽然标准库够用,但如果你对性能有极致要求,可以考虑第三方库:
- json-iterator/go:比标准库快 2-3 倍
- goccy/go-json:号称最快的 JSON 库
- segmentio/encoding/json:性能更好,API 兼容标准库
import jsoniter "github.com/json-iterator/go"
var json = jsoniter.ConfigCompatibleWithStandardLibrary
// 后续用法和标准库一样
小结
今天我们全面学习了 Go 的 JSON 处理:
- 序列化:
json.Marshal、json.MarshalIndent - 反序列化:
json.Unmarshal - 结构体标签:
json:"name"、omitempty、- - 嵌套结构:结构体嵌套和数组
- 动态 JSON:
map[string]interface{}、json.RawMessage - 自定义:
MarshalJSON、UnmarshalJSON - 流式处理:
Encoder、Decoder - 常见陷阱:未导出字段、数字类型、时间格式
JSON 是 Web 开发的基石。掌握了 Go 的 JSON 处理,你就掌握了构建 API 和服务的关键技能。
练习时间
- API 客户端:写一个调用 GitHub API 的客户端,解析返回的 JSON
- JSON 验证器:读取 JSON 文件,验证它是否符合指定的结构
- JSON 转换器:把一个格式的 JSON 转换成另一个格式(比如驼峰 → 蛇形)
- 流式处理:用
json.Decoder处理一个很大的 JSON 文件 - 自定义类型:实现一个
Duration类型,可以序列化为 “1h30m” 这种格式
下一篇预告
下一篇文章,我们将学习 HTTP 编程。Go 的标准库提供了强大的 HTTP 客户端和服务器实现,让你可以轻松构建 Web 应用。我们会学习:
- HTTP 客户端
- 构建 Web 服务器
- 路由和中间件
- 处理表单和文件上传
- 超时和错误处理
我们下篇见!👋
参考资料:
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。