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.Encoder 和 json.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.NewEncoder 和 json.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、流式处理)能应对绝大多数场景。