将地图转换为结构


94

我试图在Go中创建一个通用方法,该方法将填充struct来自的使用数据map[string]interface{}。例如,方法签名和用法可能类似于:

func FillStruct(data map[string]interface{}, result interface{}) {
    ...
}

type MyStruct struct {
    Name string
    Age  int64
}

myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"]  = 23

result := &MyStruct{}
FillStruct(myData, result)

// result now has Name set to "Tony" and Age set to 23

我知道可以使用JSON作为中介来完成;还有另一种更有效的方法吗?


1
无论如何,使用JSON作为中介都将使用反射。.假设您将使用stdlib encoding/json包来执行该中间步骤。
Simon Whitehead

是的,这就是我要避免使用JSON的原因。似乎希望有一种我不知道的更有效的方法。
tgrosinger

您可以举一个用例的例子吗?如图所示,显示一些伪代码来演示此方法将执行的操作?
西蒙·怀特黑德

嗯... unsafe包装可能有一种方法..但我不敢尝试。除此之外,还需要进行反射,因为您需要能够查询与类型关联的元数据,以便将数据放入其属性。将其包装在json.Marshal+ json.Decode调用中将是相当简单的。但这是反射的两倍。
西蒙·怀特海德2014年

我删除了有关反射的评论。我对尽可能高效地执行此操作更感兴趣。如果那意味着使用反射就可以了。
tgrosinger

Answers:


110

最简单的方法是使用https://github.com/mitchellh/mapstructure

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

如果您想自己做,可以执行以下操作:

http://play.golang.org/p/tN8mxT_V9h

func SetField(obj interface{}, name string, value interface{}) error {
    structValue := reflect.ValueOf(obj).Elem()
    structFieldValue := structValue.FieldByName(name)

    if !structFieldValue.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }

    if !structFieldValue.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    structFieldType := structFieldValue.Type()
    val := reflect.ValueOf(value)
    if structFieldType != val.Type() {
        return errors.New("Provided value type didn't match obj field type")
    }

    structFieldValue.Set(val)
    return nil
}

type MyStruct struct {
    Name string
    Age  int64
}

func (s *MyStruct) FillStruct(m map[string]interface{}) error {
    for k, v := range m {
        err := SetField(s, k, v)
        if err != nil {
            return err
        }
    }
    return nil
}

func main() {
    myData := make(map[string]interface{})
    myData["Name"] = "Tony"
    myData["Age"] = int64(23)

    result := &MyStruct{}
    err := result.FillStruct(myData)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(result)
}

1
谢谢。我使用的是经过稍微修改的版本。play.golang.org/p/_JuMm6HMnU
tgrosinger 2014年

我想要我所有各种结构上的FillStruct行为,而不必func (s MyStr...) FillStruct ...为每个结构都定义。是否可以为基本结构定义FillStruct,然后让我所有其他结构“继承”该行为?在上面的范例中,这是不可能的,因为只有基本结构...在这种情况下,“ MyStruct”实际上将对其字段进行迭代
StartupGuy 2015年

我的意思是,您可以将它用于任何具有以下结构的结构:play.golang.org/p/0weG38IUA9
dave

是否可以在Mystruct中实现标签?
vicTROLLA

1
@abhishek首先对文本进行封送处理然后取消封送处理肯定会给您带来性能上的损失。这种方法当然也更简单。这是一个折衷,通常我会选择较简单的解决方案。我回答了该解决方案,因为问题表明“我知道可以使用JSON作为中介来完成;还有其他更有效的方法吗?”。此解决方案将更加高效,JSON解决方案通常将更易于实现和推理。
戴夫

72

Hashicorp的https://github.com/mitchellh/mapstructure库开箱即用:

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

第二个result参数必须是该结构的地址。


如果映射密钥为user_name且struct字段为UserName怎么办?
尼古拉斯·耶拉


如果map kye是_id而map name是Id,那么它将不会对其进行解码。
拉维·香卡

24
  • 最简单的方法是使用encoding/json

仅举例来说:

package main
import (
    "fmt"
    "encoding/json"
)

type MyAddress struct {
    House string
    School string
}
type Student struct {
    Id int64
    Name string
    Scores float32
    Address MyAddress
    Labels []string
}

func Test() {

    dict := make(map[string]interface{})
    dict["id"] = 201902181425       // int
    dict["name"] = "jackytse"       // string
    dict["scores"] = 123.456        // float
    dict["address"] = map[string]string{"house":"my house", "school":"my school"}   // map
    dict["labels"] = []string{"aries", "warmhearted", "frank"}      // slice

    jsonbody, err := json.Marshal(dict)
    if err != nil {
        // do error check
        fmt.Println(err)
        return
    }

    student := Student{}
    if err := json.Unmarshal(jsonbody, &student); err != nil {
        // do error check
        fmt.Println(err)
        return
    }

    fmt.Printf("%#v\n", student)
}

func main() {
    Test()
}

1
谢谢@jackytse。实际上,这是最好的方法!地图结构通常不适用于接口内的嵌套地图。因此,最好考虑使用映射字符串接口并将其作为json处理。
吉尔斯·埃索基

转到上面片段的游乐场链接:play.golang.org/p/JaKxETAbsnT
Junaid

13

您可以执行此操作……它可能会变得有些丑陋,并且会在映射类型方面面临一些试验和错误..但这里是其基本要点:

func FillStruct(data map[string]interface{}, result interface{}) {
    t := reflect.ValueOf(result).Elem()
    for k, v := range data {
        val := t.FieldByName(k)
        val.Set(reflect.ValueOf(v))
    }
}

工作示例:http : //play.golang.org/p/PYHz63sbvL


1
这似乎对零值感到恐慌:reflect: call of reflect.Value.Set on zero Value
James Taylor

@JamesTaylor是的。我的答案假设您确切知道要映射的字段。如果您希望通过类似的答案得到更多的错误处理(包括您遇到的错误),那么我建议戴夫斯回答。
西蒙·怀特海德

2

我改编了dave的答案,并添加了递归功能。我仍在开发一个更加用户友好的版本。例如,映射中的数字字符串应能够在结构中转换为int。

package main

import (
    "fmt"
    "reflect"
)

func SetField(obj interface{}, name string, value interface{}) error {

    structValue := reflect.ValueOf(obj).Elem()
    fieldVal := structValue.FieldByName(name)

    if !fieldVal.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }

    if !fieldVal.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    val := reflect.ValueOf(value)

    if fieldVal.Type() != val.Type() {

        if m,ok := value.(map[string]interface{}); ok {

            // if field value is struct
            if fieldVal.Kind() == reflect.Struct {
                return FillStruct(m, fieldVal.Addr().Interface())
            }

            // if field value is a pointer to struct
            if fieldVal.Kind()==reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct {
                if fieldVal.IsNil() {
                    fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
                }
                // fmt.Printf("recursive: %v %v\n", m,fieldVal.Interface())
                return FillStruct(m, fieldVal.Interface())
            }

        }

        return fmt.Errorf("Provided value type didn't match obj field type")
    }

    fieldVal.Set(val)
    return nil

}

func FillStruct(m map[string]interface{}, s interface{}) error {
    for k, v := range m {
        err := SetField(s, k, v)
        if err != nil {
            return err
        }
    }
    return nil
}

type OtherStruct struct {
    Name string
    Age  int64
}


type MyStruct struct {
    Name string
    Age  int64
    OtherStruct *OtherStruct
}



func main() {
    myData := make(map[string]interface{})
    myData["Name"]        = "Tony"
    myData["Age"]         = int64(23)
    OtherStruct := make(map[string]interface{})
    myData["OtherStruct"] = OtherStruct
    OtherStruct["Name"]   = "roxma"
    OtherStruct["Age"]    = int64(23)

    result := &MyStruct{}
    err := FillStruct(myData,result)
    fmt.Println(err)
    fmt.Printf("%v %v\n",result,result.OtherStruct)
}

0

分两个步骤:

  1. 将接口转换为JSON字节
  2. 将JSON字节转换为结构

下面是一个示例:

dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.