JSON 处理:Go 与数据交换的桥梁

全面掌握 Go 语言的 JSON 处理:序列化、反序列化、标签、流式处理和高级技巧

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)
}

自定义序列化和反序列化

有时候默认的序列化行为不够灵活,你可以实现 MarshalerUnmarshaler 接口来自定义。

自定义 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"}

如果你需要自定义格式,要实现 MarshalJSONUnmarshalJSON

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 处理:

  1. 序列化json.Marshaljson.MarshalIndent
  2. 反序列化json.Unmarshal
  3. 结构体标签json:"name"omitempty-
  4. 嵌套结构:结构体嵌套和数组
  5. 动态 JSONmap[string]interface{}json.RawMessage
  6. 自定义MarshalJSONUnmarshalJSON
  7. 流式处理EncoderDecoder
  8. 常见陷阱:未导出字段、数字类型、时间格式

JSON 是 Web 开发的基石。掌握了 Go 的 JSON 处理,你就掌握了构建 API 和服务的关键技能。

练习时间

  1. API 客户端:写一个调用 GitHub API 的客户端,解析返回的 JSON
  2. JSON 验证器:读取 JSON 文件,验证它是否符合指定的结构
  3. JSON 转换器:把一个格式的 JSON 转换成另一个格式(比如驼峰 → 蛇形)
  4. 流式处理:用 json.Decoder 处理一个很大的 JSON 文件
  5. 自定义类型:实现一个 Duration 类型,可以序列化为 “1h30m” 这种格式

下一篇预告

下一篇文章,我们将学习 HTTP 编程。Go 的标准库提供了强大的 HTTP 客户端和服务器实现,让你可以轻松构建 Web 应用。我们会学习:

  • HTTP 客户端
  • 构建 Web 服务器
  • 路由和中间件
  • 处理表单和文件上传
  • 超时和错误处理

我们下篇见!👋


参考资料:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页