【问题标题】:function for converting a struct to map in Golang在 Golang 中将结构转换为映射的函数
【发布时间】:2014-05-11 06:14:00
【问题描述】:

我想在 Golang 中将结构转换为映射。如果我可以在创建的地图中使用 JSON 标签作为键(否则默认为字段名称),那也很好。

2020 年 12 月 14 日编辑

由于structs repo 已存档,您可以改用mapstructure。

编辑 TL;DR 版本,2015 年 6 月 15 日

如果您想要将结构转换为映射的快速解决方案,请参阅accepted answer,点赞并使用该包。

编码愉快! :)


原帖

目前我有这个功能,我用的是reflect包,但是我不太了解这个包怎么用,请多多包涵。

func ConvertToMap(model interface{}) bson.M {
    ret := bson.M{}

    modelReflect := reflect.ValueOf(model)

    if modelReflect.Kind() == reflect.Ptr {
        modelReflect = modelReflect.Elem()
    }

    modelRefType := modelReflect.Type()
    fieldsCount := modelReflect.NumField()

    var fieldData interface{}

    for i := 0; i < fieldsCount; i++ {
        field := modelReflect.Field(i)

        switch field.Kind() {
        case reflect.Struct:
            fallthrough
        case reflect.Ptr:
            fieldData = ConvertToMap(field.Interface())
        default:
            fieldData = field.Interface()
        }

        ret[modelRefType.Field(i).Name] = fieldData
    }

    return ret
}

我还查看了 JSON 包源代码,因为它应该包含我需要的实现(或其中的一部分)但不太了解。

【问题讨论】:

  • 您有什么特别想要实现的目标吗?如果您正在处理mgo/bson 包(由于使用了bson.M,这似乎是可能的),它不能已经从类似于encoding/json 的结构执行转换吗?
  • 它涉及反射,包reflect,使用起来既慢又痛苦; json 包就是这样,因为使用反射很难。我的建议是使用已经为您完成反射部分的东西(对象到数据库接口,如gorp 或mgo,内置包如json)或使用(可能重复)手写代码来避免反射完全。在这种情况下,在 JavaScript 中自然而有效的方法在 Go 中并不存在。
  • github.com/mitchellh/mapstructure - 但听詹姆斯的。在这种情况下,没有理由尝试这样做。
  • @eAbi 自述文件的第一行:“mapstructure 是一个 Go 库,用于将通用映射值解码为结构,反之亦然。” (强调我自己的)
  • @elithrar 是的,我看到了。很抱歉,我在那个库上看了好几次,但没有看到 vice versa。我在godoc 上查看了它,但看不到如何从结构转换为映射。无论如何,谢谢。

标签: go


【解决方案1】:

我也需要这样的东西。我正在使用将结构转换为地图的内部包。我决定将它与其他基于struct 的高级功能一起开源。看看:

https://github.com/fatih/structs

它支持:

  • 将结构转换为地图
  • 将结构的字段提取到[]string
  • 将结构的值提取到[]values
  • 检查结构是否已初始化
  • 检查传递的接口是结构体还是指向结构体的指针

您可以在此处查看一些示例:http://godoc.org/github.com/fatih/structs#pkg-examples 例如将结构体转换为地图很简单:

type Server struct {
    Name    string
    ID      int32
    Enabled bool
}

s := &Server{
    Name:    "gopher",
    ID:      123456,
    Enabled: true,
}

// => {"Name":"gopher", "ID":123456, "Enabled":true}
m := structs.Map(s)

structs 包支持匿名(嵌入)字段和嵌套结构。该软件包提供通过字段标签过滤某些字段。

【讨论】:

  • 哇@Fatih Arslan,这是巨大的。这实际上为map[string]interface{}s 提供了“编组”结构,就像encoding/json.Marhsal() 一样,使用结构标签等。太棒了!
  • 这是一种使用“json”标签名代替“structs”标签名的方法。 github.com/fatih/structs/issues/25
  • 请注意,此项目现已存档,没有维护。
  • 而且我不想在我的项目中添加另一个依赖项
  • 不要忘记导出结构上的字段(犯了一个菜鸟错误)
【解决方案2】:

从struct 到map[string]interface{}

package main

import (
    "fmt"
    "encoding/json"
)

type MyData struct {
    One   int
    Two   string
    Three int
}

func main() {   
    in := &MyData{One: 1, Two: "second"}

    var inInterface map[string]interface{}
    inrec, _ := json.Marshal(in)
    json.Unmarshal(inrec, &inInterface)

    // iterate through inrecs
    for field, val := range inInterface {
            fmt.Println("KV Pair: ", field, val)
    }
}

go playground here

【讨论】:

  • 为了更简单,不要使用var inInterface interface{},而是将其更改为var inInterface map[string]interface{}。在此处查看操场:play.golang.org/p/woUiMzL_1X
  • 干净简单!非常感谢!
  • 这看起来像一个黑客,我找不到任何干净或简单的东西
  • 这种方法有问题。在生成的映射中,int 值被转换为 float64,这是出乎意料的行为。 inInterface["One"] 值将是 float64 类型而不是 int。
  • 编组然后解组似乎是一种非常低效的数据转换方式。
【解决方案3】:

这是我过去编写的一个函数,用于将结构体转换为地图,使用标签作为键

// ToMap converts a struct to a map using the struct's tags.
//
// ToMap uses tags on struct fields to decide which fields to add to the
// returned map.
func ToMap(in interface{}, tag string) (map[string]interface{}, error){
    out := make(map[string]interface{})

    v := reflect.ValueOf(in)
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }

    // we only accept structs
    if v.Kind() != reflect.Struct {
        return nil, fmt.Errorf("ToMap only accepts structs; got %T", v)
    }

    typ := v.Type()
    for i := 0; i < v.NumField(); i++ {
        // gets us a StructField
        fi := typ.Field(i)
        if tagv := fi.Tag.Get(tag); tagv != "" {
            // set key of map to value in struct field
            out[tagv] = v.Field(i).Interface()
        }
    }
    return out, nil
}

可运行example here.

注意,如果您有多个具有相同标签值的字段,那么您显然无法将它们全部存储在地图中。如果发生这种情况,返回错误可能是谨慎的做法。

【讨论】:

  • 嗯,但我认为它不适用于嵌套结构,对吧?我认为必须递归遍历结构字段。
  • 你的问题没有提到这一点。那么你想要一个扁平化为地图的结构吗?
  • 是的,一个结构扁平化为一个映射(因此子结构将成为一个子映射)。如果字段包含结构,您可以通过再次调用该函数来实现这一点,对吧?
【解决方案4】:

我喜欢接受答案的可导入包,但它不会翻译我的 json 别名。我的大多数项目都有一个我导入的辅助函数/类。

这是一个解决我的具体问题的函数。


// Converts a struct to a map while maintaining the json alias as keys
func StructToMap(obj interface{}) (newMap map[string]interface{}, err error) {
    data, err := json.Marshal(obj) // Convert to a json string

    if err != nil {
        return
    }

    err = json.Unmarshal(data, &newMap) // Convert to a map
    return
}

总的来说,这就是它的名称......

package main

import (
    "fmt"
    "encoding/json"
    "github.com/fatih/structs"
)

type MyStructObject struct {
    Email string `json:"email_address"`
}

func main() {
    obj := &MyStructObject{Email: "test@test.com"}

    // My solution
    fmt.Println(StructToMap(obj)) // prints {"email_address": "test@test.com"}

    // The currently accepted solution
    fmt.Println(structs.Map(obj)) // prints {"Email": "test@test.com"}
}

【讨论】:

    【解决方案5】:
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type bill struct {
        N1 int
        N2 string
        n3 string
    }
    
    func main() {
        a := bill{4, "dhfthf", "fdgdf"}
    
        v := reflect.ValueOf(a)
    
        values := make(map[string]interface{}, v.NumField())
    
        for i := 0; i < v.NumField(); i++ {
            if v.Field(i).CanInterface() {
                values[v.Type().Field(i).Name] = v.Field(i).Interface()
            } else {
                fmt.Printf("sorry you have a unexported field (lower case) value you are trying to sneak past. I will not allow it: %v\n", v.Type().Field(i).Name)
            }
        }
    
        fmt.Println(values)
    
        passObject(&values)
    }
    
    func passObject(v1 *map[string]interface{}) {
        fmt.Println("yoyo")
    }
    

    【讨论】:

    • 这也适用于来自不同包的结构。 (请注意,您将无法访问未导出的字段)
    【解决方案6】:

    我有点晚了,但我需要这种功能,所以我写了这个。可以解析嵌套结构。默认情况下,使用字段名称,但也可以使用自定义标签。一个副作用是,如果将 tagTitle const 设置为 json,则可以使用已有的 json 标签。

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func StructToMap(val interface{}) map[string]interface{} {
        //The name of the tag you will use for fields of struct
        const tagTitle = "kelvin"
    
        var data map[string]interface{} = make(map[string]interface{})
        varType := reflect.TypeOf(val)
        if varType.Kind() != reflect.Struct {
            // Provided value is not an interface, do what you will with that here
            fmt.Println("Not a struct")
            return nil
        }
    
        value := reflect.ValueOf(val)
        for i := 0; i < varType.NumField(); i++ {
            if !value.Field(i).CanInterface() {
                //Skip unexported fields
                continue
            }
            tag, ok := varType.Field(i).Tag.Lookup(tagTitle)
            var fieldName string
            if ok && len(tag) > 0 {
                fieldName = tag
            } else {
                fieldName = varType.Field(i).Name
            }
            if varType.Field(i).Type.Kind() != reflect.Struct {
                data[fieldName] = value.Field(i).Interface()
            } else {
                data[fieldName] = StructToMap(value.Field(i).Interface())
            }
    
        }
    
        return data
    }
    
    

    【讨论】:

    • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。
    【解决方案7】:
    map := Structpb.AsMap()
    
    // map is the map[string]interface{}
    

    【讨论】:

    • 这与接受的答案有何不同?
    • @FloLie 它是预定义的库,可以减少您的工作量并返回您的期望响应。
    • 复制粘贴代码没有帮助。下次请解释一下代码的作用,在哪里找到它,以及为什么选择它而不是其他方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-30
    • 2012-06-11
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    相关资源
    最近更新 更多