← sync 包 | time 包 →

encoding/json - JSON 数据编解码

encoding/json 包提供了 JSON 数据的编解码功能,是 Go 中最常用的标准库之一。掌握 JSON 处理是开发 Web API、配置文件解析等场景的基础。

📌 核心概念

📤

Marshal

Go 结构体 → JSON

json.Marshal
📥

Unmarshal

JSON → Go 结构体

json.Unmarshal
🏷️

Struct Tag

字段映射和选项

`json:"name"`
🔧

自定义编解码

实现 json.Marshaler

MarshalJSON()

基础编解码

📝 Marshal 和 Unmarshal

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    City string `json:"city,omitempty"`
}

func main() {
    // 编码:结构体 → JSON
    p := Person{Name: "Alice", Age: 30}
    
    data, err := json.Marshal(p)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data))
    // {"name":"Alice","age":30}
    
    // 美化输出
    pretty, _ := json.MarshalIndent(p, "", "  ")
    fmt.Println(string(pretty))
    
    // 解码:JSON → 结构体
    jsonStr := `{"name":"Bob","age":25,"city":"Beijing"}`
    
    var p2 Person
    err = json.Unmarshal([]byte(jsonStr), &p2)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", p2)
    // {Name:Bob Age:25 City:Beijing}
}

💡 JSON 包要点

  • 字段导出: 只有大写开头的字段才会被编解码
  • omitempty: 零值时省略该字段
  • -: json:"-" 忽略该字段
  • 指针解码: Unmarshal 需要传递指针
  • 错误处理: 始终检查返回的 error

Struct Tag 详解

📝 JSON Tag 选项

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID       int    `json:"id"`              // 自定义字段名
    Name     string `json:"name"`            // 正常映射
    Email    string `json:"email,omitempty"` // 空值时省略
    Password string `json:"-"`               // 忽略此字段
    Role     string `json:"role,omitempty"`
}

func main() {
    // omitempty 测试
    u1 := User{ID: 1, Name: "Alice"}
    data, _ := json.Marshal(u1)
    fmt.Println(string(data))
    // {"id":1,"name":"Alice"}
    
    u2 := User{ID: 2, Name: "Bob", Email: "bob@example.com", Role: "admin"}
    data, _ = json.Marshal(u2)
    fmt.Println(string(data))
    // {"id":2,"name":"Bob","email":"bob@example.com","role":"admin"}
    
    // 嵌套结构体
    type Post struct {
        ID   int    `json:"id"`
        Title string `json:"title"`
        Author User   `json:"author"`
    }
    
    post := Post{
        ID:     1,
        Title:  "Hello World",
        Author: u2,
    }
    
    data, _ = json.Marshal(post)
    fmt.Println(string(data))
}

解码到 interface{}

📝 处理未知结构的 JSON

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    // 解码到 interface{} - 类型映射:
    // JSON bool    → bool
    // JSON number  → float64
    // JSON string  → string
    // JSON array   → []interface{}
    // JSON object  → map[string]interface{}
    // JSON null    → nil
    
    jsonStr := `{
        "name": "Alice",
        "age": 30,
        "skills": ["Go", "Python"],
        "active": true,
        "address": {
            "city": "Beijing",
            "zip": "100000"
        }
    }`
    
    var data interface{}
    json.Unmarshal([]byte(jsonStr), &data)
    
    m := data.(map[string]interface{})
    
    // 类型断言访问
    name := m["name"].(string)
    age := m["age"].(float64) // 注意:是 float64!
    active := m["active"].(bool)
    
    fmt.Printf("Name: %s, Age: %.0f, Active: %v\n", name, age, active)
    
    // 访问数组
    skills := m["skills"].([]interface{})
    for _, skill := range skills {
        fmt.Println(skill.(string))
    }
    
    // 访问嵌套对象
    address := m["address"].(map[string]interface{})
    fmt.Println(address["city"])
}

⚠️ interface{} 解码注意

  • 数字类型: JSON 数字解码为 float64,不是 int
  • 类型断言: 必须使用类型断言访问值
  • 类型检查: 使用 type switch 安全访问
  • 推荐: 优先使用结构体,类型更安全

自定义编解码

实现 Marshaler/Unmarshaler

📝 自定义 JSON 格式

package main

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

// 自定义时间格式
type Time time.Time

func (t Time) MarshalJSON() ([]byte, error) {
    s := time.Time(t).Format("2006-01-02 15:04:05")
    return json.Marshal(s)
}

func (t *Time) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    
    parsed, err := time.Parse("2006-01-02 15:04:05", s)
    if err != nil {
        return err
    }
    
    *t = Time(parsed)
    return nil
}

// 自定义类型:脱敏处理
type Password string

func (p Password) MarshalJSON() ([]byte, error) {
    return json.Marshal("***") // 脱敏输出
}

type Account struct {
    Username string `json:"username"`
    Password Password `json:"password"`
    Created  Time     `json:"created"`
}

func main() {
    acc := Account{
        Username: "alice",
        Password: "secret123",
        Created:  Time(time.Now()),
    }
    
    data, _ := json.MarshalIndent(acc, "", "  ")
    fmt.Println(string(data))
    // 密码会被脱敏
}

流式处理

📝 Encoder 和 Decoder

package main

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

func main() {
    // Encoder: 流式编码
    enc := json.NewEncoder(os.Stdout)
    enc.Encode(map[string]int{"a": 1})
    enc.Encode(map[string]int{"b": 2})
    
    // Decoder: 流式解码
    jsonData := `{"name": "Alice"} {"name": "Bob"}`
    dec := json.NewDecoder(strings.NewReader(jsonData))
    
    for {
        var v map[string]string
        if err := dec.Decode(&v); err != nil {
            break
        }
        println(v["name"])
    }
}

最佳实践

✅ JSON 使用建议

  • 使用结构体: 类型安全,代码清晰
  • 定义 Tag: 明确字段映射关系
  • 错误处理: 始终检查 error
  • 验证输入: Unmarshal 后验证数据
  • 使用 omitempty: 减少不必要的数据传输
  • 性能敏感: 考虑使用 sonic 等第三方库

🚨 常见陷阱

  • 字段未导出: 小写字段不会被编解码
  • 数字类型: interface{} 解码时数字是 float64
  • 时间格式: 默认 RFC3339 格式,可能需要自定义
  • 大数精度: float64 可能丢失 int64 精度
  • 空 vs nil: 空切片和 nil 切片编码不同

总结

✅ 核心要点

  • Marshal/Unmarshal: 基础编解码函数
  • Struct Tag: 字段映射和选项
  • omitempty: 零值时省略字段
  • 自定义编解码: 实现 Marshaler 接口
  • 流式处理: Encoder/Decoder 处理大数据
  • 类型安全: 优先使用结构体而非 interface{}