JSON 处理

标准库 encoding/json 基于反射结构体标签实现 JSON 与 Go 类型的互相转换,是 API 开发中最高频的技能之一。

编码:Marshal

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID      int64  `json:"id"`
    Name    string `json:"name"`
    Email   string `json:"email,omitempty"` // 空值时省略该字段
    Passwrd string `json:"-"`               // 永远不序列化到 JSON
}

func main() {
    u := User{ID: 1, Name: "Alice"}

    data, err := json.Marshal(u)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data))
    // {"id":1,"name":"Alice"}
}

常用结构体标签

标签效果
json:"name"字段在 JSON 中的键名
json:"-​"忽略该字段(不参与编解码)
json:",omitempty"零值时不输出
json:"name,string"以字符串形式编解码数值
Tip

未加标签的字段默认以原字段名输出(大写开头)。对外 API 应始终显式声明 json 标签,避免暴露内部命名。

格式化输出

MarshalIndentEncoder.SetIndent 可生成带缩进的 JSON,适合配置输出与调试:

data, _ := json.MarshalIndent(u, "", "  ")
fmt.Println(string(data))

解码:Unmarshal

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    input := `{"id": 42, "name": "Bob", "tags": ["go", "web"]}`

    var payload struct {
        ID   int64    `json:"id"`
        Name string   `json:"name"`
        Tags []string `json:"tags"`
    }

    if err := json.Unmarshal([]byte(input), &payload); err != nil {
        fmt.Println("解析失败:", err)
        return
    }
    fmt.Printf("%+v\n", payload)
}

动态 JSON:any 与 json.RawMessage

结构不确定时使用 map[string]any 或延迟解析:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    input := `{"name": "carol", "age": 28, "meta": {"vip": true}}`

    // 1. map[string]any:完全动态
    var m map[string]any
    _ = json.Unmarshal([]byte(input), &m)
    if meta, ok := m["meta"].(map[string]any); ok {
        fmt.Println("VIP:", meta["vip"]) // true
    }

    // 2. json.RawMessage:先保留原始字节,需要时再解析
    var raw struct {
        Name string          `json:"name"`
        Meta json.RawMessage `json:"meta"`
    }
    _ = json.Unmarshal([]byte(input), &raw)

    var vip struct {
        VIP bool `json:"vip"`
    }
    _ = json.Unmarshal(raw.Meta, &vip)
    fmt.Println(raw.Name, vip.VIP) // carol true
}

流式编解码:Decoder / Encoder

处理 HTTP 请求体、文件或网络流时,使用 json.Decoder/json.Encoder 直接读写流,无需全量加载:

package main

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

func main() {
    // 流式解码:直接读取 Reader
    r := strings.NewReader(`{"name": "dave"}`)
    var user struct {
        Name string `json:"name"`
    }
    _ = json.NewDecoder(r).Decode(&user)
    fmt.Println(user.Name) // dave

    // 流式编码:直接写入 Writer
    _ = json.NewEncoder(os.Stdout).Encode(user) // {"name":"dave"}

    // 解码 JSON Lines(每行一个对象)
    lines := strings.NewReader("{\"a\":1}\n{\"a\":2}\n")
    dec := json.NewDecoder(lines)
    for dec.More() {
        var obj map[string]any
        _ = dec.Decode(&obj)
        fmt.Println(obj["a"])
    }
}

时间与自定义编解码

time.Time 实现了 MarshalJSON/UnmarshalJSON,默认输出 RFC 3339 格式。自定义类型可实现 json.Marshalerjson.Unmarshaler 接口:

type Status int

const (
    Active Status = iota
    Inactive
)

func (s Status) MarshalJSON() ([]byte, error) {
    return []byte(`"` + map[Status]string{Active: "active", Inactive: "inactive"}[s] + `"`), nil
}

:::warning 常见陷阱:

  • 数字默认解码为 float64,大整数场景应使用 json.Number 或结构化类型。
  • Unmarshal 忽略未知字段;需要严格校验时使用 Decoder.DisallowUnknownFields()
  • 结构体字段首字母必须大写(可导出)才会参与编解码。 :::

小结

  • 结构体标签是控制 JSON 字段名与省略行为的核心。
  • 固定结构用结构体,动态结构用 map[string]anyjson.RawMessage
  • 流式场景使用 Decoder/Encoder,避免大对象全量驻留内存。