go 的 json 序列化默认通过 Struct tag 控制,无需手动反射;仅在需动态字段名、批量检查或构建通用工具时才需主动用反射解析 tag。

Go 的 json 包本身不依赖反射做序列化,但反射是实现自定义映射逻辑(比如字段名动态计算、忽略空值策略切换、嵌套结构扁平化)的底层支撑。直接用 json.Marshal 和 struct tag 就能解决 90% 的需求;只有当你需要绕过默认行为、或构建通用工具(如 ORM 字段映射器、配置注入器)时,才真正需要手动介入反射。
struct tag 是 JSON 映射的默认控制入口
Go 的 JSON 序列化优先读取 struct 字段上的 json tag,而非字段名本身。反射只是读取它的手段,不是必须绕进去写逻辑的起点。
-
json:"name":指定序列化后的键名 -
json:"name,omitempty":该字段为空值(零值)时不输出 -
json:"-":完全忽略该字段 - 未加 tag 且字段首字母小写 → 不可导出 →
json.Marshal自动跳过
例如:
type User struct { ID int `json:"id"` Name String `json:"name,omitempty"` pwd string `json:"-"` // 小写 + "-" → 完全屏蔽 }
这种写法无需反射,json.Marshal 内部已通过反射读取 tag 并处理。
用反射读取 struct tag 实现运行时字段控制
当字段名需动态生成(如根据环境加前缀)、或需批量检查哪些字段参与序列化时,才需要主动用反射提取 tag。核心是 reflect.StructTag.Get。
- 必须先用
reflect.typeof(v).Elem()获取 struct 类型(若传入指针) - 遍历
NumField,对每个Field调用Tag.Get("json") -
Tag.Get("json")返回完整 tag 字符串(如"name,omitempty"),需自行解析逗号分隔部分 - 注意:
Tag.Get对不存在的 key 返回空字符串,不是 panic
示例:提取所有非忽略字段名
func getJSONKeys(v Interface{}) []string { t := reflect.TypeOf(v) if t.Kind() == reflect.Ptr { t = t.Elem() } if t.Kind() != reflect.Struct { return nil } var keys []string for i := 0; i < t.NumField(); i++ { field := t.Field(i) jsonTag := field.Tag.Get("json") if jsonTag == "-" || jsonTag == "" { continue } key := strings.Split(jsonTag, ",")[0] if key != "" { keys = append(keys, key) } } return keys }
反射修改字段值后再 JSON 序列化需注意可导出性
想用反射给 struct 字段赋值再序列化?那字段必须是可导出的(首字母大写),否则 reflect.Value.Set 会 panic:”reflect: reflect.Value.Set using unaddressable value“。
立即学习“go语言免费学习笔记(深入)”;
- 传入的 struct 实例必须是指针(
&v),否则reflect.ValueOf(v)得到的是不可寻址副本 - 即使字段有
jsontag,若字段名小写 → 不可导出 → 反射无法 Set,json.Marshal也无法读取 - 临时构造 map[string]interface{} 再序列化更安全,适合动态场景
错误示范:
type User struct { id int `json:"id"` // 小写 → 不可导出 → 反射 Set 失败 } u := User{} rv := reflect.ValueOf(&u).Elem() rv.FieldByName("id").SetInt(123) // panic!
性能与调试成本:别为简单映射引入反射
反射调用比直接字段访问慢 10–100 倍,且破坏静态类型检查。常见误用是:本可用 map[string]interface{} 或预定义 struct 完成的映射,硬写一堆 reflect.Value.FieldByName。
- 如果只是把 http 请求 body 解成 struct → 用
json.Unmarshal+ tag - 如果要支持多种命名风格(snake_case / camelCase)自动转换 → 写一个通用的
UnmarshalJSON方法,内部用反射一次解析 tag,缓存映射表 - 如果字段逻辑复杂(如时间戳转 RFC3339、密码字段自动哈希)→ 为 struct 实现
json.Unmarshaler接口,而不是在外部用反射塞值
最易被忽略的一点:json 包对嵌套 struct、interface{}、nil slice 的处理规则,和反射获取的字段顺序、零值判断并不总是一致——调试时要同时看 JSON 输出和 fmt.printf("%+v", v) 的反射展开结果。