go 标准库 json 包已足够通用,reflect 仅被其内部用于字段遍历和值读写,不可也不应替代 json.marshal/unmarshal;因 reflect 缺乏标签解析、omitempty 语义、自定义方法调用、循环引用处理等编解码必需能力。

Go 语言标准库的 json 包本身已足够通用,reflect 包不用于“实现 JSON 转换逻辑”,而是被 json 包内部用来做字段遍历和值读写——你不需要、也不应该用 reflect 自己重写 json.Marshal 或 json.Unmarshal。
为什么不能靠 reflect 实现完整的 JSON 编解码
reflect 只提供运行时类型和值操作能力,但 JSON 编解码涉及大量边界处理:
- 结构体字段标签(
json:"name,omitempty")解析需手动匹配,reflect.StructTag只是字符串切片,不自动生效 - nil 指针、空切片、零值字段的序列化策略(如
omitempty)需逐层判断,reflect不提供语义 - 自定义
MarshalJSON/UnmarshalJSON方法必须显式调用,否则直接跳过——reflect不知道该方法存在 - 循环引用、嵌套 interface{}、未导出字段(
reflect.Value.CanInterface()失败)等场景会 panic 或静默丢数据
哪些场景真需要手动用 reflect + json 配合
典型需求不是“实现转换”,而是“在标准 json 流程中插入干预逻辑”,比如:
用于端到端视频本地化流程的轻量编排器,路由至四个专注子技能——/wjs-transcribing-audio、/wjs-translating-subtitles...
- 统一给所有字符串字段加前缀(日志脱敏)→ 在
MarshalJSON中用reflect.Value.FieldByName扫描字段,对string类型做处理 - 动态忽略某些字段(非固定 tag)→ 用
reflect.TypeOf获取结构体类型,遍历Field后按规则跳过reflect.Value.Field - 把 map[string]interface{} 的 key 全转小写 → 用
reflect.Value.MapKeys()拿 key,再用reflect.Value.SetMapIndex()重建 map - 检查 struct 是否含非 JSON 可序列化字段(如
func、chan)→ 递归调用reflect.Kind()判断
一个安全的 reflect 辅助序列化示例
以下代码只做“字段名转小写 + 忽略 func 字段”,不替代 json.Marshal,而是预处理后交给标准库:
func lowerCaseAndFilter(v interface{}) interface{} {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
if rv.Kind() != reflect.Struct {
return v
}
result := make(map[string]interface{})
t := rv.Type()
for i := 0; i
<h3>最容易被忽略的坑:interface{} 和指针穿透</h3>
<p>当你用 <code>reflect.ValueOf(x)</code> 处理 <code>interface{}</code> 或指针时,常见错误包括:</p>
- 传入
*struct{}却没调用.Elem(),导致反射对象是Ptr类型而非Struct,后续NumField()panic - 对
interface{}直接调reflect.ValueOf,若底层是 nil 接口,.Kind()返回Invalid,不报错但后续操作全失效 - 修改 struct 字段值时,原变量不是地址(如
reflect.ValueOf(s)而非reflect.ValueOf(&s).Elem()),.CanSet()返回 false - 用
reflect.Value.Interface()拿回值后,类型是interface{},若原类型是*T,此时得到的是**T,json 序列化可能多一层 null
真正需要深度控制 JSON 行为时,优先实现 json.Marshaler 接口;只有当类型不确定或需跨包动态处理时,才用 reflect 做有限干预——它不是编解码引擎,只是运行时探针。










