Go struct 转 map:三种方案的选择逻辑
在 Go 里,struct 和 map 各有用途:struct 是强类型、编译期检查的领域模型,map 是动态键值对,适合序列化、动态字段处理、模板渲染等场景。当两者之间需要互转时,有三条路可走,选哪条取决于你对类型信息、性能和字段控制的要求。
一个前置约束
无论用哪种方法,struct 里参与转换的字段首字母必须大写(exported)。小写字段对包外代码(包括 reflect 和 encoding/json)不可见,不会出现在转换结果里。这不是方法的限制,是 Go 的可见性规则。
方案一:JSON 序列化
1 | |
它做了什么:先把 struct 序列化成 JSON 字节,再反序列化成 map[string]interface{}。
关键行为:
- 完全遵守
jsontag。字段名用 tag 指定的 key,json:"-"的字段会被跳过,omitempty会过滤零值。 - 数值类型会变成
float64(JSON 数字的默认类型),嵌套 struct 会变成map[string]interface{}。 - 有两次内存分配(marshal + unmarshal),性能开销最高。
适合的场景:你的 struct 本来就有 json tag,且你接受类型信息的损耗(比如 int 变 float64)。大多数 Web 开发场景里,这个方案直接够用。
方案二:反射
1 | |
它做了什么:直接遍历 struct 的字段,取字段名和值,填入 map。
关键行为:
- map 的 key 是字段名(Go 标识符),不读取
jsontag。如果 struct 字段是UserName,key 就是"UserName",而不是可能配置的"user_name"。 - 保留原始类型信息:
int就是int,不会变成float64。 - 不自动处理嵌套 struct——嵌套字段会以 struct 值的形式存在 map 里,不会递归展开。
- 性能比 JSON 方案好,但反射本身有开销,热路径上需要评估。
适合的场景:你需要保留原始类型,且不在乎 json tag,或者你自己写逻辑来读取 tag。
方案三:第三方库 fatih/structs
1 | |
它做了什么:基于反射实现,但功能更完整。
关键行为:
- 支持
structstag 控制 key 名和忽略字段(类似jsontag 的用法)。 - 默认递归处理嵌套 struct。
- 提供额外工具函数:
structs.Names()、structs.Values()、structs.IsZero()等。
适合的场景:你需要 struct ↔ map 的完整工具集,且愿意引入外部依赖。该库已停止维护(last commit 2018),如果项目有严格的依赖审查要求,需要评估风险。
横向对比
| JSON 序列化 | reflect | fatih/structs | |
|---|---|---|---|
| key 来源 | json tag / 字段名 | 字段名 | structs tag / 字段名 |
| 类型保留 | 否(数值变 float64) | 是 | 是 |
| 嵌套 struct | 递归展开为 map | 保留为 struct 值 | 递归展开为 map |
| 零值字段 | 支持 omitempty | 全量输出 | 可配置 |
| 外部依赖 | 无 | 无 | 有(且停止维护) |
| 相对性能 | 最低 | 中 | 中 |
选择建议
- 日常 Web 开发:直接用 JSON 方案,行为可预期,和序列化逻辑对齐。
- 需要保留类型 / 性能敏感:用 reflect 方案,自行处理 tag 逻辑。
- fatih/structs:除非已有存量依赖,新项目不建议引入。
没有”最好”的方案,只有和你的场景匹配的方案。
Go struct 转 map:三种方案的选择逻辑
https://www.krli.org/2018/04/13/Go-语言-struct转map的三种方法/