Go 入门(13):JSON 序列化——encoding/json 与 struct tag
更新时间:2026-09-01。本文是
languages/go/beginner/入门层第 13 篇,接 时间处理。Go 对 JSON 的支持是标准库的一部分,用 struct tag 控制序列化行为,简洁高效。
本文要回答的问题
- struct tag 是什么?json tag 怎么控制字段名?
omitempty选项做什么?什么时候用?- JSON 反序列化时,数字类型是什么?为什么 int 会 panic?
- 怎么处理嵌套的 JSON 对象?
一、struct tag 控制 JSON 字段名
Go 结构体默认用字段名做 JSON 的 key,但你可以用 struct tag 控制:
go
type User struct {
Name string `json:"name"` // 序列化时 key 是 "name"
Age int `json:"age"` // 序列化时 key 是 "age"
Email string `json:"email,omitempty"` // 空值时忽略
Phone string `json:"-"` // 永远不序列化
Address string `json:"address,omitempty"`
}tag 语法:
json:"name"→ 字段名改成namejson:"-"→ 忽略这个字段,不参与 JSON 序列化json:"name,omitempty"→ 字段名是name,空值时忽略(不输出)json:",omitempty"→ 保留字段名,只加 omitempty 选项
二、Marshal:结构体 → JSON
go
u := User{
Name: "Alice",
Age: 25,
Email: "alice@example.com",
}
// 序列化
data, err := json.Marshal(u)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data)) // {"name":"Alice","age":25,"email":"alice@example.com"}
// 格式化输出(带缩进)
data, _ = json.MarshalIndent(u, "", " ")
fmt.Println(string(data))
// {
// "name": "Alice",
// "age": 25,
// "email": "alice@example.com"
// }三、Unmarshal:JSON → 结构体
go
jsonStr := `{"name":"Bob","age":30,"email":"bob@example.com"}`
var u User
err := json.Unmarshal([]byte(jsonStr), &u)
if err != nil {
log.Fatal(err)
}
fmt.Println(u.Name) // Bob注意:Unmarshal 的参数是 []byte,所以字符串要转成 []byte。
四、最大的坑:JSON 数字默认是 float64
当你用 map[string]interface{} 来接收 JSON 时,JSON 数字默认解析成 float64,不是 int:
go
jsonStr := `{"age": 25, "score": 90.5}`
var data map[string]interface{}
json.Unmarshal([]byte(jsonStr), &data)
age := data["age"].(float64) // 是 float64,不是 int!
// age := data["age"].(int) // panic!所以从 map 取数字时,要断言成 float64 再转:
go
age := int(data["age"].(float64)) // 先 float64 再转 int为什么? JSON 规范没有区分整数和浮点,Go 的 encoding/json 默认把数字解析为 float64,保证都能存下。如果你知道结构体结构,用结构体接收就不会有这个问题。
五、处理嵌套 JSON
go
type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type User struct {
Name string `json:"name"`
Address Address `json:"address"`
}
jsonStr := `{"name":"Alice","address":{"city":"Beijing","country":"China"}}`
var u User
json.Unmarshal([]byte(jsonStr), &u)
fmt.Println(u.Address.City) // Beijing六、JSON 序列化时间
go
type Event struct {
Name string `json:"name"`
Time time.Time `json:"time"`
}
e := Event{Name: "meeting", Time: time.Now()}
data, _ := json.Marshal(e)
fmt.Println(string(data))
// {"name":"meeting","time":"2026-09-01T14:30:00+08:00"}
// time.Time 默认序列化为 RFC3339 格式七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 字段名首字母小写 | JSON 序列化不出来,字段是空的 | 导出字段首字母大写 |
| tag 写错 | 忽略 tag,用字段名 | 仔细检查 tag 字符串 |
| omitempty 忽略空值 | 零值字段(0、false、空串)被忽略 | 期望始终输出不用 omitempty |
| 数字解析为 float64 | 断言成 int 会 panic | 用结构体接收,或断言成 float64 再转 |
| 大整数精度丢失 | 超过 2^53 的整数 float64 存不下 | 用 json.Number 或自定义类型 |
相关与延伸
下一篇:文件 I/O——os 与 bufio;JSON 和 Go 类型映射,对应进阶层;自定义 JSON 序列化(实现 Marshaler/Unmarshaler 接口),见进阶层。
一句话总结
Go JSON 处理:json.Marshal 结构体转 JSON,json.Unmarshal JSON 转结构体;struct tag 用 json:"字段名,选项" 控制序列化,omitempty 空值忽略,- 跳过字段;最大陷阱是 JSON 数字默认解析成 float64,断言成 int 会 panic,用结构体接收或断言成 float64 再转 int。