跳到主要内容

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 对各类型的零值是什么?

答案

类型零值(会被省略)
boolfalse
int/float0
string""
pointernil
slice/mapnil(注意:空但非 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 类型

相关链接