不该自己用反射实现JSON序列化,因为标准库已完备处理字段可见性、标签解析、嵌套结构、循环引用及零值跳过等;自行实现无法复用hand-written优化,性能差3–10倍,且易忽略omitempty等标签语义、不完整支持嵌入字段和MarshalJSON方法。

Go 的 json.Marshal 本身已基于反射实现,手动用反射重写 JSON 序列化既没必要,也极易出错——标准库已处理了字段可见性、标签解析、嵌套结构、循环引用(报错而非崩溃)、零值跳过等全部边界情况。
为什么不该自己用反射实现 JSON 序列化
标准库的 json.Marshal 内部确实大量使用反射(reflect.Value 和 reflect.Type),但它还依赖大量 hand-written 优化路径(如对 []byte、string、基础类型直接编码,绕过反射);自行实现时:
- 无法复用这些优化,性能通常差 3–10 倍
- 容易忽略
json:",omitempty"、json:"name,string"等标签语义 - 对匿名字段、嵌入结构体、指针 nil 值、自定义
MarshalJSON方法支持不完整 - 遇到未导出字段会静默跳过,而新手常误以为“反射能读私有字段”——实际不能,
reflect.Value.Field对非导出字段 panic
想控制序列化行为?优先用 struct tag 和接口
真正需要定制时,应通过标准方式介入,而非重写反射逻辑:
- 用
json:"field_name,omitempty"控制键名与零值省略 - 为类型实现
MarshalJSON() ([]byte, error)方法(注意:必须是导出类型,且方法签名严格匹配) - 对敏感字段加
json:"-"完全屏蔽 - 若需运行时动态决定是否序列化某字段,可封装一个包装类型,内部用指针 + 自定义
MarshalJSON
例如:
立即学习“go语言免费学习笔记(深入)”;
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Token *string `json:"token,omitempty"` // nil 时不输出
}
真要动手玩反射?只用于调试或元数据提取
反射适合做「序列化前的检查」或「生成 schema」这类只读分析,而非编码本身:
- 遍历结构体字段并打印 tag:
sf := t.Field(i); sf.Tag.Get("json") - 检查字段是否实现了
json.Marshaler接口:fv.Type().Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem().Type()) - 发现未导出字段但被错误期望序列化(此时应改字段为导出,而非强行反射读取)
注意:reflect.Value.Interface() 在字段不可寻址或非导出时会 panic,务必先用 fv.CanInterface() 和 fv.CanAddr() 判断。
反射不是序列化的替代方案,而是标准库背后沉默的工人。你该关心的是 struct tag 是否写对、接口是否正确定义、指针是否意外为 nil——而不是去重造一个更慢、更脆、更难维护的 json.Marshal。










