2014-11-04 23 views
40

Tôi đang cố gắng tạo phương thức chung trong Go để điền số struct sử dụng dữ liệu từ map[string]interface{}. Ví dụ: chữ ký và cách sử dụng phương thức có thể trông giống như:Chuyển đổi bản đồ thành cấu trúc

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 

Tôi biết điều này có thể được thực hiện bằng cách sử dụng JSON làm trung gian; có cách nào hiệu quả hơn để làm việc này không?

+1

Sử dụng JSON làm trung gian sẽ sử dụng phản chiếu anyway .. giả sử bạn sẽ sử dụng gói 'encoding/json' stdlib để thực hiện bước trung gian đó .. Bạn có thể đưa ra một ví dụ về bản đồ và ví dụ về cấu trúc mà phương thức này có thể được sử dụng trên? –

+0

Vâng, đó là lý do tôi đang cố gắng tránh JSON. Có vẻ như hy vọng là một phương pháp hiệu quả hơn mà tôi không biết. – tgrosinger

+0

Bạn có thể đưa ra ví dụ về trường hợp sử dụng không? Như trong - hiển thị một số giả mã chứng minh những gì phương pháp này sẽ làm gì? –

Trả lời

35

Đây là ý tưởng giống như câu trả lời của Simon, nhưng với một chút xử lý lỗi hơn:

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

Rất đẹp +1 :) –

+1

Cảm ơn bạn. Tôi đang sử dụng một phiên bản được sửa đổi một chút. http://play.golang.org/p/_JuMm6HMnU – tgrosinger

+0

Tôi muốn hành vi FillStruct trên tất cả các cấu trúc khác nhau của tôi và không phải định nghĩa 'func (s MyStr ...) FillStruct ...' cho mọi thứ. Có thể xác định FillStruct cho một cấu trúc cơ bản sau đó có tất cả các cấu trúc khác của tôi 'kế thừa' hành vi đó? Trong mô hình trên nó không thể vì chỉ có cấu trúc cơ bản ... trong trường hợp này "MyStruct" sẽ thực sự có các trường được lặp lại –

10

Bạn có thể làm điều đó ... nó có thể nhận được một chút xấu xí và bạn sẽ phải đối mặt với một số thử và sai về các loại bản đồ .. nhưng heres ý chính cơ bản của nó:

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

mẫu làm việc: http://play.golang.org/p/PYHz63sbvL

+0

Điều này dường như hoảng loạn về giá trị bằng không: 'phản ánh: cuộc gọi của reflect.Value.Set trên không Giá trị' –

+0

@JamesTaylor Có. Câu trả lời của tôi giả định bạn biết chính xác trường nào bạn đang ánh xạ. Nếu bạn sau một câu trả lời tương tự với xử lý lỗi nhiều hơn (bao gồm cả lỗi bạn đang gặp phải), tôi sẽ đề nghị Daves trả lời thay thế. –

0

Tôi thích ứng với câu trả lời của dave và thêm tính năng đệ quy. Tôi vẫn đang làm việc trên một phiên bản thân thiện với người dùng hơn. Ví dụ, một chuỗi số trong bản đồ sẽ có thể được chuyển đổi thành int trong cấu trúc. thư viện https://github.com/mitchellh/mapstructure

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 của thực hiện điều này ra khỏi hộp:

import "github.com/mitchellh/mapstructure" 

mapstructure.Decode(myData, &result) 

Tham số thứ hai result phải là một địa chỉ của cấu trúc.

+3

Điều này thật tuyệt vời, cảm ơn bạn.Không có điểm nào tự thực hiện nó – Brian

+3

Cảm ơn bạn! – shapeshifter

Các vấn đề liên quan