encoding/json
本教程共 80 篇 · 第 75 篇 · 更新于 2026-07-27 · 约 8 分钟阅读
75. encoding/json
本节目标:学会用 Marshal/Unmarshal 编解码 JSON,掌握结构体标签,了解流式编解码和自定义序列化。
序列化 Marshal
json.Marshal 把 Go 值转成 JSON 字节:
type User struct {
Name string
Age int
}
u := User{Name: "Tom", Age: 18}
data, err := json.Marshal(u)
fmt.Println(string(data)) // {"Name":"Tom","Age":18}
想输出更易读的格式,用 MarshalIndent:
data, _ := json.MarshalIndent(u, "", " ")
fmt.Println(string(data))
输出:
{
"Name": "Tom",
"Age": 18
}
反序列化 Unmarshal
json.Unmarshal 把 JSON 解析回 Go 值:
jsonStr := `{"Name":"Tom","Age":18}`
var u User
err := json.Unmarshal([]byte(jsonStr), &u)
if err != nil {
fmt.Println("解析失败:", err)
}
fmt.Println(u) // {Tom 18}
注意传的是 &u(指针),Unmarshal 需要修改原变量。
结构体标签
默认 JSON 的 key 就是字段名(大写开头)。实际开发里我们需要小写或改名的 key,用标签控制:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"` // 空值省略
Addr string `json:"-"` // 忽略这个字段
}
效果:
u := User{Name: "Tom", Age: 18}
data, _ := json.Marshal(u)
// {"name":"Tom","age":18}
常用标签:
| 标签 | 作用 |
|---|---|
json:"name" | key 改成 name |
json:",omitempty" | 零值时不输出 |
json:"-" | 完全忽略 |
json:"name,omitempty" | 改名且空值省略 |
Tip
omitempty对零值生效:字符串空串、数字 0、bool false、指针 nil、切片/map 为 nil。如果想保留 0 值输出,用指针类型。
字段类型对应
| Go 类型 | JSON 类型 |
|---|---|
string | string |
int/float64 | number |
bool | boolean |
[]T | array |
map[string]T | object |
*T(指针) | 对应类型或 null |
any / interface{} | 任意类型 |
解析不确定结构
JSON 结构不确定时,先解析到 map[string]any:
var result map[string]any
json.Unmarshal([]byte(`{"name":"Tom","tags":["a","b"],"score":95.5}`), &result)
fmt.Println(result["name"]) // Tom
fmt.Println(result["tags"]) // [a b]
fmt.Println(result["score"]) // 95.5
Warning
map[string]any里的数字都是float64类型。要 int 得自己转:int(result["score"].(float64))。不确定结构最好还是定义结构体,类型更安全。
流式编码 Encoder
大数据量时用 json.Encoder 流式写入,不用一次占满内存:
file, _ := os.Create("users.json")
defer file.Close()
encoder := json.NewEncoder(file)
users := []User{
{Name: "Tom", Age: 18},
{Name: "Jerry", Age: 20},
}
for _, u := range users {
encoder.Encode(u) // 每次写一个对象,自动换行
}
流式解码 Decoder
file, _ := os.Open("users.json")
defer file.Close()
decoder := json.NewDecoder(file)
for {
var u User
err := decoder.Decode(&u)
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Println(u)
}
NoteDecoder 适合处理 JSON 数组流或多个连续 JSON 对象。HTTP 请求 body 解析也常用
json.NewDecoder(req.Body).Decode(&v),省内存。
自定义 MarshalJSON
实现 json.Marshaler 接口可以自定义序列化逻辑:
type Color struct {
R, G, B uint8
}
func (c Color) MarshalJSON() ([]byte, error) {
hex := fmt.Sprintf("\"#%02X%02X%02X\"", c.R, c.G, c.B)
return []byte(hex), nil
}
func (c *Color) UnmarshalJSON(data []byte) error {
// data 形如 "#FF8800"
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
fmt.Sscanf(s, "#%02X%02X%02X", &c.R, &c.G, &c.B)
return nil
}
效果:
c := Color{255, 136, 0}
data, _ := json.Marshal(c)
fmt.Println(string(data)) // "#FF8800"
Go 1.26 新特性
Go 1.26 的 new(expr) 对 JSON 可选字段很实用。比如 age 字段用 *int,有值时 new(年龄) 直接生成指针:
type Person struct {
Name string `json:"name"`
Age *int `json:"age"` // 指针,nil 表示未知
}
func personJSON(name string, age int) ([]byte, error) {
return json.Marshal(Person{
Name: name,
Age: new(age), // 直接把 age 的值传给 new
})
}
以前要写成 Age: &[]int{age}[0],现在清爽多了。
小结
Marshal序列化,Unmarshal反序列化,注意传指针- 结构体标签
json:"name,omitempty"控制 key 名和空值省略 json:"-"忽略字段- 不确定结构用
map[string]any,数字是float64 Encoder/Decoder流式编解码,省内存- 自定义
MarshalJSON/UnmarshalJSON控制序列化行为 - Go 1.26
new(expr)方便生成可选字段指针
下一节讲 net/http 基础。