Go struct 转 map:三种方案的选择逻辑

在 Go 里,struct 和 map 各有用途:struct 是强类型、编译期检查的领域模型,map 是动态键值对,适合序列化、动态字段处理、模板渲染等场景。当两者之间需要互转时,有三条路可走,选哪条取决于你对类型信息、性能和字段控制的要求。

一个前置约束

无论用哪种方法,struct 里参与转换的字段首字母必须大写(exported)。小写字段对包外代码(包括 reflectencoding/json)不可见,不会出现在转换结果里。这不是方法的限制,是 Go 的可见性规则。


方案一:JSON 序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
import "encoding/json"

func StructToMap(obj interface{}) (map[string]interface{}, error) {
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
return nil, err
}
return m, nil
}

它做了什么:先把 struct 序列化成 JSON 字节,再反序列化成 map[string]interface{}

关键行为

  • 完全遵守 json tag。字段名用 tag 指定的 key,json:"-" 的字段会被跳过,omitempty 会过滤零值。
  • 数值类型会变成 float64(JSON 数字的默认类型),嵌套 struct 会变成 map[string]interface{}
  • 有两次内存分配(marshal + unmarshal),性能开销最高。

适合的场景:你的 struct 本来就有 json tag,且你接受类型信息的损耗(比如 int 变 float64)。大多数 Web 开发场景里,这个方案直接够用。


方案二:反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "reflect"

func StructToMap(obj interface{}) map[string]interface{} {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)

// 处理指针
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}

m := make(map[string]interface{}, t.NumField())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if !field.IsExported() {
continue
}
m[field.Name] = v.Field(i).Interface()
}
return m
}

它做了什么:直接遍历 struct 的字段,取字段名和值,填入 map。

关键行为

  • map 的 key 是字段名(Go 标识符),不读取 json tag。如果 struct 字段是 UserName,key 就是 "UserName",而不是可能配置的 "user_name"
  • 保留原始类型信息:int 就是 int,不会变成 float64
  • 不自动处理嵌套 struct——嵌套字段会以 struct 值的形式存在 map 里,不会递归展开。
  • 性能比 JSON 方案好,但反射本身有开销,热路径上需要评估。

适合的场景:你需要保留原始类型,且不在乎 json tag,或者你自己写逻辑来读取 tag。


方案三:第三方库 fatih/structs

1
2
3
import "github.com/fatih/structs"

m := structs.Map(obj)

它做了什么:基于反射实现,但功能更完整。

关键行为

  • 支持 structs tag 控制 key 名和忽略字段(类似 json tag 的用法)。
  • 默认递归处理嵌套 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的三种方法/
作者
李科燃
发布于
2018年4月13日
许可协议