Go 的 json.Marshal 本质依赖反射实现:通过 reflect.ValueOf 获取字段信息,仅导出字段可被访问,标签控制键名或忽略,未导出字段无论有无标签均被跳过;实现 MarshalJSON 方法可绕过反射提升性能。

Go 的 json.Marshal 本质就是靠反射工作的
你写的 json.Marshal(obj) 看似简单,但它内部会调用 reflect.ValueOf(obj) 获取结构体字段的类型、标签、值等信息。没有反射,标准库就无法自动遍历结构体字段、读取 json: 标签、跳过未导出字段——这些都不是编译期能确定的事。
这意味着:只要用了 encoding/json,你就已经在用反射了;想绕开它,只能手写序列化逻辑或换用不依赖反射的库(如 easyjson 或 ffjson)。
json.Marshal 对字段可见性的处理完全由反射控制
Go 反射只能访问导出(首字母大写)字段,json.Marshal 正是基于这一点实现“默认忽略私有字段”。但注意,这和“是否带 json: 标签”无关——即使给私有字段加了 json:"name",它依然不会被序列化,因为反射根本读不到它的 reflect.StructField。
- 导出字段 + 无
json:标签 → 使用字段名小写形式作为 key - 导出字段 +
json:"foo"→ 使用"foo"作为 key - 导出字段 +
json:"-"→ 被忽略(反射能读到,但 json 包显式跳过) - 未导出字段(如
name string)→ 反射无法获取,直接跳过,加任何标签都无效
自定义 MarshalJSON 方法会绕过反射字段遍历
当类型实现了 MarshalJSON() ([]byte, error),json.Marshal 会直接调用它,不再走反射路径。这是性能优化的关键出口,也是控制序列化行为最彻底的方式。
立即学习“go语言免费学习笔记(深入)”;
本文档主要讲述的是JSON.NET 简单的使用;JSON.NET使用来将.NET中的对象转换为JSON字符串(序列化),或者将JSON字符串转换为.NET中已有类型的对象(反序列化?)。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
常见使用场景:
- 隐藏敏感字段(比如不把
PasswordHash写进 JSON) - 序列化时做计算(如把
CreatedAt time.Time转成 Unix 时间戳整数) - 避免反射开销(高频小结构体,实测可提升 2–5 倍吞吐)
func (u User) MarshalJSON() ([]byte, error) {
type Alias User // 防止无限递归
return json.Marshal(&struct {
*Alias
CreatedAt int64 `json:"created_at"`
}{
Alias: (*Alias)(&u),
CreatedAt: u.CreatedAt.Unix(),
})
}
反射带来的性能代价在高并发 JSON 场景下不可忽视
每次 json.Marshal 都要动态检查结构体布局、解析标签、分配临时 map/slice——这些操作无法被编译器优化,且会触发内存分配。压测中常见现象:
- GC 压力明显上升(尤其
map[string]interface{}类型) - CPU profile 显示大量时间花在
reflect.Value.Field和strings.Split(解析 tag)上 - 结构体字段越多、嵌套越深,反射耗时越非线性增长
如果服务每秒处理上万次 JSON 序列化,且结构体稳定,建议提前用 go:generate 工具(如 easyjson)生成无反射的 MarshalJSON 实现——它把反射过程移到了编译期。









