encoding/json
问题
Go 的 JSON 序列化和反序列化怎么用?struct tag 有哪些选项?如何自定义编解码?
答案
基本用法
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"` // 零值时省略
Secret string `json:"-"` // 永远不参与序列化
}
// 序列化
user := User{Name: "Alice", Age: 30}
data, err := json.Marshal(user)
// {"name":"Alice","age":30} // email 省略了(零值 + omitempty)
// 反序列化
var user2 User
err = json.Unmarshal(data, &user2)
// 美化输出
data, _ = json.MarshalIndent(user, "", " ")
struct tag 选项
| Tag | 说明 | 示例 |
|---|---|---|
json:"name" | 指定 JSON 字段名 | json:"user_name" |
json:",omitempty" | 零值时省略 | json:"age,omitempty" |
json:"-" | 忽略该字段 | json:"-" |
json:",string" | 数字/布尔作为字符串 | json:"id,string" |
流式编解码(io.Reader/Writer)
// 从 Reader 解码
decoder := json.NewDecoder(r.Body)
var user User
err := decoder.Decode(&user)
// 编码到 Writer
encoder := json.NewEncoder(w)
err := encoder.Encode(user)
Marshal vs Encoder
json.Marshal:返回[]byte,适合小数据json.NewEncoder:写入io.Writer,适合 HTTP 响应、文件等流式场景,性能更好(不需要中间 buffer)
自定义编解码
type Time time.Time
func (t Time) MarshalJSON() ([]byte, error) {
stamp := time.Time(t).Format("2006-01-02")
return json.Marshal(stamp) // 注意:用 json.Marshal 添加引号
}
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", s)
if err != nil {
return err
}
*t = Time(parsed)
return nil
}
处理动态 JSON
// 不确定结构时用 map
var result map[string]any
json.Unmarshal(data, &result)
// json.RawMessage 延迟解析
type Event struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"` // 先不解析
}
var event Event
json.Unmarshal(data, &event)
// 根据 Type 再解析 Payload
switch event.Type {
case "user":
var user User
json.Unmarshal(event.Payload, &user)
}
性能注意事项
| 方案 | 性能 | 说明 |
|---|---|---|
encoding/json | 基准 | 标准库,基于反射 |
json-iterator | ~2-3x 快 | 兼容标准库 API |
sonic | ~5-10x 快 | JIT 编译,字节跳动出品 |
encoding/json/v2(实验) | ~2-3x 快 | 官方新版 JSON 包(开发中) |
常见面试问题
Q1: json.Marshal 对 nil slice 和空 slice 的处理有什么区别?
答案:
var nilSlice []int // nil
emptySlice := []int{} // 非 nil,长度为 0
json.Marshal(nilSlice) // "null"
json.Marshal(emptySlice) // "[]"
API 返回数组时,应该用 []int{} 而不是 var []int,避免返回 null。
Q2: omitempty 对各类型的零值是什么?
答案:
| 类型 | 零值(会被省略) |
|---|---|
bool | false |
int/float | 0 |
string | "" |
pointer | nil |
slice/map | nil(注意:空但非 nil 不省略) |
struct | 不支持 omitempty |
Q3: 如何处理 JSON 中数字精度问题?
答案:
JSON 中的数字默认解析为 float64,大整数(如 int64 雪花 ID)会丢失精度:
// 前端 JavaScript Number 最大安全整数:2^53 - 1
// 超过会丢失精度
// 解决方案:序列化为字符串
type User struct {
ID int64 `json:"id,string"` // 输出 "123456789" 而不是 123456789
}
// 或使用 json.Number
decoder := json.NewDecoder(reader)
decoder.UseNumber() // 数字保持为 json.Number 类型