1. 基础标签解析
1.1 常用的 json 标签字段
在 Go 语言中,JSON 序列化通常通过 encoding/json 包完成,而结构体的 Tag 是影响序列化结果的关键点。结构体字段的 JSON 名称、是否忽略字段、以及是否忽略空值等都由 json 标签控制,让你可以灵活地把 Go 的字段映射到需要的 JSON 结构上。
最常见的用法是 json:\"name\",它指定字段在 JSON 中的名称;json:\"-\" 可以排除某个字段;omitempty 表示如果字段为空则不输出;string 则把原本数字、布尔等字段以字符串形式输出,避免默认的类型转换产生歧义。
type User struct {ID int `json:"id"`Name string `json:"name,omitempty"`Password string `json:"-"`Created int64 `json:"created_at"`
}
通过以上标签组合,你可以实现字段控制、隐藏敏感信息、以及对输出格式的微调,而无需在序列化逻辑内再做额外处理。
1.2 标签选项与行为
除了命名和忽略,结构体标签还可以结合选项实现更多行为,例如将时间以字符串输出、将数字输出为字符串等。合理使用 omitempty 可以显著减少网络传输的体积,尤其在字段很多且多数为零值或空值的场景下。
示例中,若某字段为空值则不会出现在输出的 JSON 中,这通常有助于保持 API 的干净性与向后兼容性。
type Response struct {Code int `json:"code"`Msg string `json:"message,omitempty"`Data *Result `json:"data,omitempty"`
}
2. 自定义序列化实现
2.1 MarshalJSON 的设计要点
当默认的编码路径不能满足性能与格式要求时,可以通过实现 MarshalJSON 来
设计要点包括:保持与外部 API 的兼容性、避免无限递归、以及尽量复用已有结构来降低维护成本。

type User struct {ID intName string// 其他字段
}// 自定义序列化:通过别名结构体避免递归调用
func (u User) MarshalJSON() ([]byte, error) {type alias Userreturn json.Marshal(&struct {ID int `json:"id"`Name string `json:"name"`// 这里可以控制输出的字段与格式Extra string `json:"extra,omitempty"`*alias}{ID: u.ID,Name: u.Name,Extra: "custom",alias: (*alias)(&u),})
}
通过使用别名结构体,可以避免对同一个类型进行再次递归的 Marshal 调用,从而实现更可控的序列化行为。
2.2 UnmarshalJSON 的对称性与容错
与 MarshalJSON 对应,UnmarshalJSON 让你在反序列化阶段进行自定义解析。还原输入时的容错处理、字段转化、以及对异常数据的保护都需要在这里统一管理。
实现时要确保字段对齐、类型转换正确,以及尽量提供合理的默认值或错误信息,以便调用方在遇到错误时有清晰的反馈。
type User struct {ID intName stringTime time.Time
}func (u *User) UnmarshalJSON(data []byte) error {// 先用类型别名避免无限递归type alias Useraux := &struct {Time string `json:"time"`*alias}{ alias: (*alias)(u) }if err := json.Unmarshal(data, &aux); err != nil {return err}// 自定义解析 Time 字段t, err := time.Parse(time.RFC3339, aux.Time)if err != nil {return err}u.Time = treturn nil
}
3. 性能优化技巧
3.1 避免反射开销与重复分配
默认的 json 编码路径会大量使用反射,容易成为性能瓶颈。通过自定义序列化、最小化不必要的反射、以及减少对象分配可以显著提升吞吐量。
在高并发场景,复用缓冲区与编码器实例成为常见的优化手段,能够降低 GC 频率和分配次数。
var bufPool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) },
}func writeJSON(w io.Writer, v interface{}) error {buf := bufPool.Get().(*bytes.Buffer)defer bufPool.Put(buf)buf.Reset()if err := json.NewEncoder(buf).Encode(v); err != nil {return err}_, err := w.Write(buf.Bytes())return err
}
3.2 使用高效的编码路径与流式编码
Streaming Encoder(json.Encoder)比一次性生成整段 JSON 的 Marshal Path 更友好于海量数据的场景,且可在写入端就完成输出,减少中间对象。
在吞吐优先的场景下,建议使用 json.NewEncoder 配合流式写入目标,如网络连接或文件,以避免将整个对象一次性加载到内存中。
enc := json.NewEncoder(w)
if err := enc.Encode(obj); err != nil {// 处理错误
}
4. 进阶实战要点
4.1 与 time.Time、自定义类型的序列化
时间字段通常是性能和格式的焦点。对 time.Time 的默认序列化为 RFC3339 字符串,在某些场景下会产生较大字符串与解析成本。通过自定义封装类型并实现 MarshalJSON,可将时间以更紧凑的格式输出,或统一时区、毫秒精度等要求。
在高频写入时间字段的场景下,优先考虑自定义序列化路径,以避免重复的时间格式化开销。
type TS struct {T time.Time
}func (t TS) MarshalJSON() ([]byte, error) {// 使用紧凑格式,避免多次重复的时间格式化s := t.T.Format("2006-01-02T15:04:05.000Z07:00")return []byte(`"` + s + `"`), nil
}
4.2 编码策略与错误处理
在生产环境中,健壮的错误处理与清晰的返回值是关键,尤其在对外接口和数据传输时。你可以在自定义序列化中返回特定错误,或实现自定义类型的错误信息汇总,以便调用方快速定位问题。
并发写入时,线程安全的编码路径与对齐的锁粒度很重要,避免因为并发写入导致的竞争和性能下降。
type Item struct {ID int `json:"id"`
}func (i Item) MarshalJSON() ([]byte, error) {// 自定义序列化,确保并发写时的线程安全return json.Marshal(struct {ID string `json:"id"`}{ID: fmt.Sprintf("%d", i.ID)})
}


