引入

http 包 处理 HTTP 请求时,数据交换最常用的格式就是 JSON。手动拼 JSON 字符串既容易出错又不优雅,Go 的 encoding/json 包让结构体和 JSON 之间的转换自动完成。

正文

定义

定义

encoding/json 包提供 JSON 编码和解码 功能。Marshal 把 Go 值序列化为 JSON 字节切片,Unmarshal 把 JSON 字节切片反序列化为 Go 值。通过结构体标签控制字段映射关系。

语法

import "encoding/json"
 
// 编码:Go 值 → JSON
data, err := json.Marshal(v)
 
// 解码:JSON → Go 值
err = json.Unmarshal(data, &v)

例子

Marshal:结构体转 JSON

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
 
u := User{Name: "Tom", Age: 20}
data, err := json.Marshal(u)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))  // {"name":"Tom","age":20}

json.Marshal 返回 []byte,用 string() 转换后就能看。结构体标签 `json:"name"` 指定 JSON 字段名。

Unmarshal:JSON 转结构体

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
 
jsonData := []byte(`{"name":"Tom","age":20}`)
 
var u User
err := json.Unmarshal(jsonData, &u)
if err != nil {
    log.Fatal(err)
}
fmt.Println(u.Name, u.Age)  // Tom 20

json.Unmarshal 第一个参数是 JSON 字节切片,第二个是指针&u)。JSON 字段名通过标签匹配到结构体字段。

结构体标签详解

type Product struct {
    ID       int     `json:"id"`                // 指定 JSON 字段名
    Name     string  `json:"name,omitempty"`    // 零值时省略
    Price    float64 `json:"-"`                 // 完全忽略此字段
    internal string                              // 小写字段不会被编码
}
  • json:"name" → JSON 字段名为 name
  • json:",omitempty"零值时不输出(空字符串、0、nil、false)
  • json:"-"完全忽略,不编码也不解码
  • 首字母小写的字段默认不会被 JSON 包处理

MarshalIndent:格式化 JSON

u := User{Name: "Tom", Age: 20}
data, _ := json.MarshalIndent(u, "", "  ")
fmt.Println(string(data))
// {
//   "name": "Tom",
//   "age": 20
// }

MarshalIndent 生成带缩进的 JSON,第二个参数是前缀,第三个是缩进字符串。调试和写配置文件时很好用。

解码到 map

jsonData := []byte(`{"name":"Tom","age":20}`)
 
var m map[string]interface{}
json.Unmarshal(jsonData, &m)
 
fmt.Println(m["name"])  // Tom
fmt.Println(m["age"])   // 20(float64 类型)

不知道 JSON 结构时,可以解码到 map[string]interface{}。注意 JSON 数字会解析为 float64

json.Encoder 和 json.Decoder

// 写入 HTTP 响应
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
encoder.Encode(user)
 
// 从 HTTP 请求体读取
var u User
decoder := json.NewDecoder(r.Body)
decoder.Decode(&u)

Encoder / Decoder 直接对接 io.Writer / io.Reader省去了中间字节切片。在 http 包 的 Handler 里最常用。

处理嵌套 JSON

type Address struct {
    City    string `json:"city"`
    Country string `json:"country"`
}
 
type Person struct {
    Name    string  `json:"name"`
    Address Address `json:"address"`
}
 
jsonData := []byte(`{"name":"Tom","address":{"city":"Beijing","country":"China"}}`)
 
var p Person
json.Unmarshal(jsonData, &p)
fmt.Println(p.Address.City)  // Beijing

嵌套结构体对应嵌套 JSON,结构关系自动匹配

常见写法

写法说明
json.Marshal(v)Go 值转 JSON 字节切片
json.MarshalIndent(v, "", " ")Go 值转格式化 JSON
json.Unmarshal(data, &v)JSON 字节切片转 Go 值
json.NewEncoder(w).Encode(v)直接写入 io.Writer
json.NewDecoder(r).Decode(&v)直接从 io.Reader 读取
`json:"name"`指定 JSON 字段名
`json:",omitempty"`零值时省略
`json:"-"`忽略字段

特点

  • Marshal 返回 []byte,不是 string,打印时要 string() 转换
  • Unmarshal 第二个参数必须是指针
  • 结构体标签控制字段映射,是 Go 里最优雅的元数据方式
  • omitempty 在 API 响应中很常用,避免返回一堆无意义的零值字段
  • JSON 数字默认解析为 float64,需要整数要手动类型断言
  • Encoder / Decoder 配合 io.Writer / io.Reader 效率更高,省内存
  • 只有首字母大写的字段才会被 JSON 包处理

理解

encoding/json 的核心就两个操作:Marshal 把结构体变成 JSON,Unmarshal 把 JSON 变回结构体。结构体标签是灵魂——它告诉 JSON 包”这个字段叫什么名字、什么时候省略、要不要忽略”。在 HTTP 接口里配合 Encoder / Decoder 用,几行代码就能完成 JSON 数据的收发,非常顺手。

引出

JSON 里经常要处理时间字符串(如 "2026-07-20T15:30:00Z"),解析和格式化时间需要专门的时间处理。接下来看 Go time 包,学习 Go 中时间的创建、格式化和计算。