Перетворення карти в структуру


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 як посередника в будь-якому випадку використовуватиме відображення .. припускаючи, що ви будете використовувати encoding/jsonпакет stdlib для виконання цього проміжного кроку .. Чи можете ви навести приклад карти та приклад структури, на яких можна використовувати цей метод?
Simon Whitehead

Так, саме тому я намагаюся уникати JSON. Здається, існує надійніший метод, про який я не знаю.
tgrosinger

Чи можете ви навести приклад варіанту використання? Як у - показати якийсь псевдокод, який демонструє, що цей метод буде робити?
Саймон Уайтхед

Ммм ... може бути спосіб із unsafeпакунком .. але я не наважуюся його спробувати. Крім цього .. Відображення потрібне, оскільки вам потрібно мати можливість запитувати метадані, пов’язані з типом, щоб розмістити дані у його властивостях. Було б досить прямо обернути це у json.Marshal+ json.Decodeдзвінки .. але це подвійне відображення.
Саймон Уайтхед

Я видалив свій коментар щодо роздумів. Мені більше цікаво просто робити це якомога ефективніше. Якщо це означає використання рефлексії, це нормально.
tgrosinger

Відповіді:


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

Я хочу поведінку FillStruct у всіх моїх різних структурах і не повинен визначати func (s MyStr...) FillStruct ...для кожної. Чи можна визначити FillStruct для базової структури, щоб тоді всі інші мої структури `` успадковували '' таку поведінку? У наведеній вище парадигмі це неможливо, оскільки лише базова структура ... у цьому випадку "MyStruct" насправді матиме
ітерацію

Я маю на увазі, що ви можете змусити
Дейв

Чи можна реалізувати теги в Mystruct?
vicTROLLA

1
@abhishek, безумовно, передбачено покарання за продуктивність, яке ви заплатите за спочатку розміщення тексту в тексті, а потім за демаршалінг. Цей підхід, безумовно, також простіший. Це компроміс, і, як правило, я піду на більш просте рішення. Я відповів цим рішенням, оскільки в запитанні було зазначено: "Я знаю, що це можна зробити, використовуючи JSON як посередника; чи існує інший більш ефективний спосіб зробити це?". Це рішення буде більш ефективним, рішення JSON, як правило, простішим у впровадженні та міркуванням.
Дейв

72

Бібліотека https://github.com/mitchellh/mapstructure Hashicorp робить це нестандартно:

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

Другим resultпараметром має бути адреса структури.


що робити, якщо ключ карти є, user_nameа структурований файл є UserName?
Микола Джела

1
@NicholasJela це може впоратись із тегами godoc.org/github.com/mitchellh/mapstructure#ex-Decode--Tags
Схема в стіні

що якщо карта kye має _id, а назва карти - 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.
Gilles Essoki

Перейдіть за посиланням на майданчик для вищевказаного фрагмента: 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
Джеймс Тейлор

@JamesTaylor Так. Моя відповідь передбачає, що ви точно знаєте, які поля ви відображаєте. Якщо ви шукаєте подібну відповідь із більшою кількістю обробок помилок (включаючи помилку, яку ви зазнали), я б запропонував відповісти Дейву.
Саймон Уайтхед,

2

Я адаптую відповідь Дейва і додаю рекурсивну функцію. Я все ще працюю над більш зручною версією. Наприклад, числовий рядок на карті повинен мати можливість перетворити на 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 Byte
  2. Перетворити JSON Byte у struct

Нижче наведено приклад:

dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.