2014-11-04 64 views
40

我想在Go中创建一个通用方法,它将使用来自map[string]interface{}的数据填充struct。例如,方法签名和用法可能如下所示:将映射转换为结构

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'包来完成这个中间步骤..您可以给出一个示例映射和示例结构,用于? – 2014-11-04 21:09:05

+0

是的,这是我试图避免JSON的原因。似乎希望是一种我不知道的更有效的方法。 – tgrosinger 2014-11-04 22:01:26

+0

你能举一个例子用例吗?如展示一些伪代码来演示这种方法将会做什么? – 2014-11-04 22:02:21

回答

35

这是相同的思路西蒙的答案,但有一点更多的错误处理:

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) 
} 
+0

非常好+1 :) – 2014-11-04 22:58:26

+1

谢谢。我正在使用一个稍微修改过的版本。 http://play.golang.org/p/_JuMm6HMnU – tgrosinger 2014-11-04 22:58:32

+0

我想在我所有的结构中使用FillStruct行为,而不必为每个结构定义'func(s MyStr ...)FillStruct ...'。是否有可能为基础结构定义FillStruct,然后让我所有的其他结构继承这种行为?在上面的范例中,它是不可能的,因为只有基本结构...在这种情况下“MyStruct”实际上将它的字段迭代为 – 2015-08-12 23:31:10

10

你可以做到这一点...它可能会有点难看,你会面临着映射类型方面的一些试验和错误..但继承人它的基本要点是:

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

+0

这似乎在零值恐慌:'反映:调用reflect.Value.Set零值' – 2017-04-23 04:17:12

+0

@JamesTaylor是的。我的答案假设你确切地知道你正在映射哪些字段。如果您在处理更多错误(包括您遇到的错误)的类似答案之后,我会建议Daves进行回答。 – 2017-04-23 05:00:01

0

我修改了戴夫的答案,并添加了递归功能。我仍在研究更加用户友好的版本。例如,映射中的数字字符串应该可以在结构中转换为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) 
} 
36

Hashicorp的https://github.com/mitchellh/mapstructure库做这个开箱:

import "github.com/mitchellh/mapstructure" 

mapstructure.Decode(myData, &result) 

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

+3

这很好,谢谢。没有意义,我们自己实施 – Brian 2017-05-26 18:56:19

+3

另一种感谢! – shapeshifter 2017-09-20 07:44:43