2014-11-04 62 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