Karte in Struktur konvertieren

92

Ich versuche, eine generische Methode in Go zu erstellen, die eine structVerwendung von Daten aus a füllt map[string]interface{}. Die Signatur und Verwendung der Methode könnte beispielsweise folgendermaßen aussehen:

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

Ich weiß, dass dies mit JSON als Vermittler möglich ist. Gibt es eine andere effizientere Möglichkeit, dies zu tun?

tgrosinger
quelle
1
Wenn Sie JSON als Vermittler verwenden, wird die Reflektion ohnehin verwendet. Angenommen, Sie verwenden das stdlib- encoding/jsonPaket, um diesen Zwischenschritt auszuführen . Können Sie eine Beispielzuordnung und eine Beispielstruktur angeben, für die diese Methode verwendet werden könnte?
Simon Whitehead
Ja, das ist der Grund, warum ich versuche, JSON zu vermeiden. Es scheint, als gäbe es hoffentlich eine effizientere Methode, die ich nicht kenne.
Tgrosinger
Können Sie einen Anwendungsbeispiel nennen? Wie in - einen Pseudocode zeigen, der zeigt, was diese Methode bewirkt?
Simon Whitehead
Mmm ... es könnte einen Weg mit dem unsafePaket geben ... aber ich wage es nicht, es zu versuchen. Davon abgesehen ist eine Reflexion erforderlich, da Sie in der Lage sein müssen, die einem Typ zugeordneten Metadaten abzufragen, um Daten in seine Eigenschaften einzufügen. Es wäre ziemlich einfach, dies in json.Marshal+ json.DecodeAnrufe einzuwickeln . Aber das ist doppelt so viel .
Simon Whitehead
Ich habe meinen Kommentar zur Reflexion entfernt. Ich bin mehr daran interessiert, dies so effizient wie möglich zu machen. Wenn das bedeutet, Reflexion zu verwenden, ist das in Ordnung.
Tgrosinger

Antworten:

109

Am einfachsten wäre es, https://github.com/mitchellh/mapstructure zu verwenden

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

Wenn Sie es selbst tun möchten, können Sie Folgendes tun:

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)
}
Dave
quelle
1
Danke dir. Ich verwende eine leicht modifizierte Version. play.golang.org/p/_JuMm6HMnU
tgrosinger
Ich möchte das FillStruct-Verhalten für alle meine verschiedenen Strukturen und muss es nicht func (s MyStr...) FillStruct ...für jede definieren . Ist es möglich, FillStruct für eine Basisstruktur zu definieren, damit alle meine anderen Strukturen dieses Verhalten "erben"? Im obigen Paradigma ist dies nicht möglich, da nur die Basisstruktur ... in diesem Fall "MyStruct" tatsächlich die Felder iteriert
StartupGuy
Ich meine, Sie könnten es für jede Struktur mit so etwas funktionieren lassen
Dave
Ist es möglich, Tags in Mystruct zu implementieren?
VicTROLLA
1
@abhishek Es gibt sicherlich eine Leistungsstrafe, die Sie bezahlen müssen, wenn Sie zuerst Text zusammenstellen und dann nicht mehr zusammenstellen. Dieser Ansatz ist sicherlich auch einfacher. Es ist ein Kompromiss, und im Allgemeinen würde ich mich für die einfachere Lösung entscheiden. Ich antwortete mit dieser Lösung, weil die Frage lautete: "Ich weiß, dass dies mit JSON als Vermittler möglich ist. Gibt es eine andere effizientere Möglichkeit, dies zu tun?". Diese Lösung wird effizienter sein, die JSON-Lösung wird im Allgemeinen einfacher zu implementieren und zu überlegen sein.
Dave
71

Die https://github.com/mitchellh/mapstructure-Bibliothek von Hashicorp erledigt dies sofort :

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

Der zweite resultParameter muss eine Adresse der Struktur sein.

Yunspace
quelle
Was ist, wenn der Kartenschlüssel user_nameund die Struktur abgelegt sind UserName?
Nicholas Jela
1
@NicholasJela es kann das mit Tags godoc.org/github.com/mitchellh/mapstructure#ex-Decode--Tags
Schaltung in der Wand
Was ist, wenn Map Kye _id und Map Name Id ist, wird es nicht dekodiert.
Ravi Shankar
21
  • Der einfachste Weg, dies zu tun, ist die Verwendung eines encoding/jsonPakets

nur zum Beispiel:

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()
}
Jackytse
quelle
1
Danke @jackytse. Dies ist eigentlich der beste Weg, das zu tun !! Die Kartenstruktur funktioniert nicht oft mit verschachtelten Karten innerhalb einer Schnittstelle. Betrachten Sie daher besser eine Map-String-Schnittstelle und behandeln Sie sie als JSON.
Gilles Essoki
Gehen Sie zum Spielplatz-Link für das obige Snippet: play.golang.org/p/JaKxETAbsnT
Junaid
13

Sie können es tun ... es kann ein bisschen hässlich werden und Sie werden mit einigen Versuchen und Irrtümern in Bezug auf Zuordnungstypen konfrontiert sein ... aber hier ist das Wesentliche:

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

Arbeitsbeispiel: http://play.golang.org/p/PYHz63sbvL

Simon Whitehead
quelle
1
Dies scheint bei Nullwerten in Panik zu geraten:reflect: call of reflect.Value.Set on zero Value
James Taylor
@ JamesTaylor Ja. Meine Antwort setzt voraus, dass Sie genau wissen, welche Felder Sie zuordnen. Wenn Sie nach einer ähnlichen Antwort mit mehr Fehlerbehandlung suchen (einschließlich des aufgetretenen Fehlers), würde ich stattdessen Daves Antwort vorschlagen.
Simon Whitehead
2

Ich passe Daves Antwort an und füge eine rekursive Funktion hinzu. Ich arbeite immer noch an einer benutzerfreundlicheren Version. Beispielsweise sollte eine Zahlenzeichenfolge in der Map in der Struktur in int konvertiert werden können.

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)
}
rox
quelle
0

Es gibt zwei Schritte:

  1. Konvertieren Sie die Schnittstelle in JSON-Byte
  2. Konvertieren Sie JSON-Byte in Struktur

Unten ist ein Beispiel:

dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)
Nick L.
quelle