目录

Go JSON 与编码

JSON(JavaScript Object Notation)是现代应用中最常用的数据交换格式。Go 标准库的 encoding/json 包提供了完善的 JSON 编解码能力。此外,Go 还支持 XML、CSV、gob 等多种编码格式。掌握这些编码工具是构建网络服务和数据处理的必备技能。

JSON 编码(Marshal)

json.Marshal 将 Go 数据结构转换为 JSON 字节切片。

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name    string
	Age     int
	Email   string
	Hobbies []string
}

func main() {
	p := Person{
		Name:    "张三",
		Age:     28,
		Email:   "zhangsan@example.com",
		Hobbies: []string{"编程", "阅读", "游泳"},
	}

	// 基本编码
	data, err := json.Marshal(p)
	if err != nil {
		fmt.Println("编码失败:", err)
		return
	}
	fmt.Println("JSON:", string(data))

	// 格式化输出(带缩进)
	pretty, err := json.MarshalIndent(p, "", "  ")
	if err != nil {
		fmt.Println("编码失败:", err)
		return
	}
	fmt.Println("格式化 JSON:\n", string(pretty))

	// 编码 map
	m := map[string]interface{}{
		"name": "李四",
		"age":  30,
		"active": true,
	}
	mapData, _ := json.MarshalIndent(m, "", "  ")
	fmt.Println("\nMap JSON:\n", string(mapData))
}

默认情况下,json.Marshal 使用结构体字段名作为 JSON 的 key。导出的字段(首字母大写)才会被编码,未导出的字段会被忽略。

JSON 解码(Unmarshal)

json.Unmarshal 将 JSON 数据解析为 Go 数据结构。

package main

import (
	"encoding/json"
	"fmt"
	"strings"
)

type User struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Email string `json:"email,omitempty"`
}

func main() {
	jsonStr := `{"name": "王五", "age": 25, "email": "wangwu@example.com"}`

	// 解码到结构体
	var user User
	err := json.Unmarshal([]byte(jsonStr), &user)
	if err != nil {
		fmt.Println("解码失败:", err)
		return
	}
	fmt.Printf("用户: %+v\n", user)

	// 解码到 map(适用于结构未知的 JSON)
	var result map[string]interface{}
	json.Unmarshal([]byte(jsonStr), &result)
	fmt.Printf("Name: %v, Age: %v\n", result["name"], result["age"])
	// 注意:数字会被解码为 float64

	// 解码到 interface{}(使用 json.Number 保留数字精度)
	decoder := json.NewDecoder(strings.NewReader(jsonStr))
	decoder.UseNumber()
	var generic interface{}
	decoder.Decode(&generic)
	m := generic.(map[string]interface{})
	fmt.Printf("Age type: %T, value: %v\n", m["age"], m["age"])
}

解码到 map[string]interface{} 时,JSON 数字默认被解析为 float64。如果需要精确的数字类型,可以使用 decoder.UseNumber()

Struct Tag 控制序列化

通过 struct tag 可以精确控制 JSON 的字段名、是否忽略空值等。

package main

import (
	"encoding/json"
	"fmt"
)

type Product struct {
	ID       int      `json:"id"`
	Name     string   `json:"name"`
	Price    float64  `json:"price"`
	Desc     string   `json:"description,omitempty"` // 空字符串时省略
	Internal string   `json:"-"`                      // 永远不序列化
	Tags     []string `json:"tags,omitempty"`          // nil 或空切片时省略
}

func main() {
	p := Product{
		ID:       1,
		Name:     "Go 语言编程",
		Price:    59.9,
		Desc:     "",  // 空字符串,omitempty 会省略此字段
		Internal: "内部编号", // json:"-" 不会被序列化
		Tags:     nil,
	}

	data, _ := json.MarshalIndent(p, "", "  ")
	fmt.Println(string(data))
	// 输出中没有 description、Internal 和 tags 字段

	// 带值的版本
	p2 := Product{
		ID:    2,
		Name:  "Go 并发编程",
		Price: 79.9,
		Desc:  "深入理解 Go 并发模型",
		Tags:  []string{"并发", "高级"},
	}

	data2, _ := json.MarshalIndent(p2, "", "  ")
	fmt.Println("\n", string(data2))
}

常用 tag 规则:

  • json:"name" — 指定 JSON 字段名
  • json:"-" — 忽略该字段
  • json:",omitempty" — 零值时省略
  • json:"name,omitempty" — 指定名称且零值时省略

自定义 MarshalJSON / UnmarshalJSON

当默认的序列化行为不满足需求时,可以实现自定义的 JSON 编解码。

package main

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

// Date 自定义日期类型的 JSON 格式
type Date struct {
	time.Time
}

func (d Date) MarshalJSON() ([]byte, error) {
	formatted := fmt.Sprintf(`"%s"`, d.Time.Format("2006-01-02"))
	return []byte(formatted), nil
}

func (d *Date) UnmarshalJSON(data []byte) error {
	s := strings.Trim(string(data), `"`)
	t, err := time.Parse("2006-01-02", s)
	if err != nil {
		return err
	}
	d.Time = t
	return nil
}

// Status 自定义枚举类型
type Status int

const (
	StatusActive   Status = 1
	StatusInactive Status = 0
)

func (s Status) MarshalJSON() ([]byte, error) {
	if s == StatusActive {
		return json.Marshal("active")
	}
	return json.Marshal("inactive")
}

func (s *Status) UnmarshalJSON(data []byte) error {
	var str string
	if err := json.Unmarshal(data, &str); err != nil {
		return err
	}
	switch str {
	case "active":
		*s = StatusActive
	case "inactive":
		*s = StatusInactive
	default:
		return fmt.Errorf("unknown status: %s", str)
	}
	return nil
}

type Event struct {
	Name   string `json:"name"`
	Date   Date   `json:"date"`
	Status Status `json:"status"`
}

func main() {
	e := Event{
		Name:   "Go 发布会",
		Date:   Date{Time: time.Date(2024, 6, 15, 0, 0, 0, 0, time.UTC)},
		Status: StatusActive,
	}

	data, _ := json.MarshalIndent(e, "", "  ")
	fmt.Println("序列化:\n", string(data))

	// 反序列化
	jsonStr := `{"name": "Go 大会", "date": "2024-12-01", "status": "inactive"}`
	var e2 Event
	json.Unmarshal([]byte(jsonStr), &e2)
	fmt.Printf("\n反序列化: %+v\n", e2)
	fmt.Printf("日期: %s, 状态: %v\n", e2.Date.Format("2006-01-02"), e2.Status)
}

实现 MarshalJSON() ([]byte, error) 接口控制序列化行为,实现 UnmarshalJSON([]byte) error 接口控制反序列化行为。

JSON 流式编解码

对于大量数据或网络流,使用 json.Encoderjson.Decoder 进行流式处理更高效。

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"
)

type LogEntry struct {
	Level   string `json:"level"`
	Message string `json:"message"`
}

func main() {
	// === Encoder 流式编码 ===
	var buf bytes.Buffer
	encoder := json.NewEncoder(&buf)
	encoder.SetIndent("", "  ") // 可选:设置缩进

	entries := []LogEntry{
		{Level: "INFO", Message: "服务启动"},
		{Level: "WARN", Message: "内存使用率较高"},
		{Level: "ERROR", Message: "数据库连接失败"},
	}

	for _, entry := range entries {
		if err := encoder.Encode(entry); err != nil {
			fmt.Println("编码失败:", err)
		}
	}
	fmt.Println("编码结果:\n", buf.String())

	// === Decoder 流式解码 ===
	jsonStream := `{"level":"INFO","message":"请求处理"}
{"level":"DEBUG","message":"缓存命中"}
{"level":"ERROR","message":"超时"}`

	decoder := json.NewDecoder(strings.NewReader(jsonStream))
	for decoder.More() {
		var entry LogEntry
		if err := decoder.Decode(&entry); err != nil {
			fmt.Println("解码失败:", err)
			break
		}
		fmt.Printf("[%s] %s\n", entry.Level, entry.Message)
	}
}

json.NewEncoderjson.NewDecoder 可以对接任何实现了 io.Writer/io.Reader 的数据源,包括文件、网络连接、内存缓冲等。

其他编码格式简介

Go 标准库还支持多种编码格式,API 设计与 encoding/json 类似。

package main

import (
	"bytes"
	"encoding/csv"
	"encoding/gob"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"os"
)

// XML 示例
type Config struct {
	XMLName xml.Name `xml:"config"`
	Host    string   `xml:"host"`
	Port    int      `xml:"port"`
	Debug   bool     `xml:"debug"`
}

func xmlExample() {
	c := Config{Host: "localhost", Port: 8080, Debug: true}
	data, _ := xml.MarshalIndent(c, "", "  ")
	fmt.Println("XML:\n", string(data))

	var c2 Config
	xml.Unmarshal(data, &c2)
	fmt.Printf("XML 解码: %+v\n\n", c2)
}

// CSV 示例
func csvExample() {
	var buf bytes.Buffer
	writer := csv.NewWriter(&buf)

	records := [][]string{
		{"姓名", "年龄", "城市"},
		{"张三", "28", "北京"},
		{"李四", "32", "上海"},
	}
	writer.WriteAll(records)
	fmt.Println("CSV:\n", buf.String())

	// CSV 读取
	reader := csv.NewReader(bytes.NewBufferString(buf.String()))
	allRecords, _ := reader.ReadAll()
	for _, record := range allRecords {
		fmt.Println(record)
	}
}

// gob 示例(Go 特有的二进制编码)
type Message struct {
	From string
	To   string
	Body string
}

func gobExample() {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)

	msg := Message{From: "alice", To: "bob", Body: "你好"}
	enc.Encode(msg)
	fmt.Printf("\ngob 编码大小: %d 字节\n", buf.Len())

	var decoded Message
	dec := gob.NewDecoder(&buf)
	dec.Decode(&decoded)
	fmt.Printf("gob 解码: %+v\n", decoded)
}

func main() {
	xmlExample()
	csvExample()
	gobExample()

	// JSON 对比:相同数据的 JSON 大小
	msg := Message{From: "alice", To: "bob", Body: "你好"}
	jsonData, _ := json.Marshal(msg)
	fmt.Printf("\nJSON 编码大小: %d 字节\n", len(jsonData))

	// 写入文件示例
	os.WriteFile("/tmp/go-demo/data.json", jsonData, 0644)
}

各编码格式的特点:

  • JSON:通用、可读性好,适合 API 交互
  • XML:标签式结构,适合配置文件和 SOAP 协议
  • CSV:表格数据,适合数据导入导出
  • gob:Go 特有的二进制格式,高效紧凑,适合 Go 程序间通信

总结

Go 的编码体系以 encoding/json 为核心,提供了结构体 tag、自定义序列化、流式编解码等完整功能。struct tag 是控制序列化行为的关键工具。json.Encoder/Decoder 适合处理流式数据。除 JSON 外,标准库还支持 XML、CSV、gob 等格式,API 风格统一。在实际项目中,JSON 是最常用的格式,掌握其各种技巧(omitempty、自定义 Marshal/Unmarshal、流式处理)能应对绝大多数场景。