Chuyển đổi bản đồ thành cấu trúc


94

Tôi đang cố gắng tạo một phương thức chung trong Go sẽ điền vào một phương thức structsử dụng dữ liệu từ a map[string]interface{}. Ví dụ: chữ ký phương thức và cách sử dụng có thể giống như sau:

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 khác hiệu quả hơn để làm điều này không?


1
Việc sử dụng JSON làm trung gian vẫn sẽ sử dụng phản chiếu .. giả sử bạn sẽ sử dụng encoding/jsongói stdlib để thực hiện bước trung gian đó .. Bạn có thể đưa ra một bản đồ ví dụ và cấu trúc ví dụ mà phương pháp này có thể được sử dụng không?
Simon Whitehead

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

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

Mmm ... có thể có một cách với unsafegói .. nhưng tôi không dám thử nó. Ngoài điều đó ra .. Cần có phản chiếu, vì bạn cần có thể truy vấn siêu dữ liệu được liên kết với một loại để đặt dữ liệu vào các thuộc tính của nó. Sẽ khá dễ dàng nếu kết hợp điều này trong các cuộc gọi json.Marshal+ json.Decode.. nhưng đó là sự phản ánh gấp đôi.
Simon Whitehead

Tôi đã xóa bình luận của tôi về phản ánh. Tôi quan tâm hơn đến việc làm điều này càng hiệu quả càng tốt. Nếu điều đó có nghĩa là sử dụng phản chiếu thì không sao.
tgrosinger

Câu trả lời:


110

Cách đơn giản nhất là sử dụng https://github.com/mitchellh/maposystem

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

Nếu bạn muốn tự làm, bạn có thể làm như sau:

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
Cảm ơn bạn. Tôi đang sử dụng một phiên bản sửa đổi một chút. play.golang.org/p/_JuMm6HMnU
tgrosinger

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

Tôi có nghĩa là bạn có thể làm cho nó hoạt động cho bất kỳ cấu trúc nào như thế này: play.golang.org/p/0weG38IUA9
dave

Có thể triển khai các thẻ trong Mystruct không?
vicTROLLA

1
@abhishek chắc chắn có một hình phạt hiệu suất mà bạn sẽ phải trả cho việc sắp xếp đầu tiên để nhắn tin và sau đó tháo quản lý. Cách làm đó chắc chắn cũng đơn giản hơn. Đó là một sự đánh đổi, và nói chung tôi sẽ chọn giải pháp đơn giản hơn. Tôi đã trả lời bằng giải pháp này vì câu hỏi đã nêu "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 khác hiệu quả hơn để thực hiện việc này không?". Giải pháp này sẽ hiệu quả hơn, giải pháp JSON nói chung sẽ dễ triển khai và lý luận hơn.
dave

72

Thư viện https://github.com/mitchellh/maposystem của Hashicorp thực hiện điều này một cách hiệu quả:

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

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


điều gì sẽ xảy ra nếu khóa bản đồ là user_namevà cấu trúc được đệ trình UserName?
Nicholas Jela

1
@NicholasJela nó có thể xử lý rằng với thẻ godoc.org/github.com/mitchellh/mapstructure#ex-Decode--Tags
Circuit trên tường

điều gì sẽ xảy ra nếu bản đồ kye là _id và tên bản đồ là Id thì nó sẽ không giải mã được.
Ravi Shankar

24
  • cách đơn giản nhất để làm điều đó là sử dụng encoding/jsongói

chỉ ví dụ:

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
Cảm ơn @jackytse. Đây thực sự là cách tốt nhất để làm điều đó !! cấu trúc bản đồ thường không hoạt động với bản đồ lồng nhau trong một giao diện. Vì vậy, tốt hơn hết hãy xem xét một giao diện chuỗi bản đồ và xử lý nó như một json.
Gilles Essoki


13

Bạn có thể làm điều đó ... nó có thể hơi xấu và bạn sẽ phải đối mặt với một số thử nghiệm và sai sót về các loại ánh xạ .. nhưng đó là ý 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


1
Điều này dường như gây hoảng sợ đối với các giá trị 0:reflect: call of reflect.Value.Set on zero Value
James Taylor

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

2

Tôi điều chỉnh câu trả lời của dave và thêm một 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 hơn với người dùng. Ví dụ, một chuỗi số trong bản đồ có thể được chuyển đổi thành int trong cấu trúc.

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

Có hai bước:

  1. Chuyển đổi giao diện sang JSON Byte
  2. Chuyển đổi JSON Byte thành struct

Dưới đây là một ví dụ:

dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.