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

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

一个前置约束

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


方案一:JSON 序列化

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{}。

关键行为:

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


方案二:反射

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。

关键行为:

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


方案三:第三方库 fatih/structs

import "github.com/fatih/structs"

m := structs.Map(obj)

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

关键行为:

适合的场景:你需要 struct ↔ map 的完整工具集,且愿意引入外部依赖。该库已停止维护(last commit 2018),如果项目有严格的依赖审查要求,需要评估风险。


横向对比

JSON 序列化 reflect fatih/structs
key 来源 json tag / 字段名 字段名 structs tag / 字段名
类型保留 否(数值变 float64) 是 是
嵌套 struct 递归展开为 map 保留为 struct 值 递归展开为 map
零值字段 支持 omitempty 全量输出 可配置
外部依赖 无 无 有(且停止维护)
相对性能 最低 中 中

选择建议

没有“最好”的方案,只有和你的场景匹配的方案。

GolangGolang
← 所有文章