2014-11-04 9 views
40

Ich versuche, eine generische Methode in Go zu erstellen, die eine struct mit Daten aus einer map[string]interface{} füllen wird. Zum Beispiel könnte die Methodensignatur und Nutzung wie folgt aussehen:Konvertieren von Karte in Struktur

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ß, das JSON als Vermittler durchgeführt werden kann; Gibt es einen anderen effizienteren Weg, dies zu tun?

+1

Wenn Sie JSON als Vermittler verwenden, verwenden Sie sowieso reflection .. vorausgesetzt, Sie verwenden das stdlib 'encoding/json'-Paket, um diesen Zwischenschritt auszuführen. Können Sie eine Beispielzuordnung und Beispielstruktur für diese Methode angeben? benutzt werden? –

+0

Ja, deshalb versuche ich, JSON zu vermeiden. Es scheint, dass es hoffentlich eine effizientere Methode gibt, von der ich nichts weiß. – tgrosinger

+0

Können Sie einen Anwendungsfall angeben? Wie in - zeige einige Pseudocode, die zeigt, was diese Methode tun wird? –

Antwort

35

Dies ist die gleiche Idee wie Simons Antwort, aber mit ein wenig mehr Fehlerbehandlung:

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

Sehr schön +1 :) –

+1

Vielen Dank. Ich verwende eine leicht modifizierte Version. http://play.golang.org/p/_JuMm6HMnU – tgrosinger

+0

Ich möchte das FillStruct-Verhalten auf allen meinen verschiedenen Strukturen und nicht definieren müssen 'func (s MyStr ...) FillStruct ...' für jeden. Ist es möglich, FillStruct für eine Basisstruktur zu definieren, dann haben alle meine anderen Strukturen dieses Verhalten geerbt? Im obigen Paradigma ist es nicht möglich, da nur die Basisstruktur ... in diesem Fall "MyStruct" tatsächlich seine Felder haben wird iteriert –

10

Sie können es tun ... es kann ein bisschen hässlich, und Sie werden mit einigen Versuch und Irrtum in Bezug auf die Mapping-Arten .. aber heres die grundlegenden Kern von ihm konfrontiert werden:

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

Arbeits Beispiel: http://play.golang.org/p/PYHz63sbvL

+0

Dies scheint bei Nullwerten in Panik zu geraten: 'reflect: Aufruf von reflect.Value.Set auf Null Wert' –

+0

@JamesTaylor Ja. Meine Antwort geht davon aus, dass Sie genau wissen, welche Felder Sie zuordnen. Wenn Sie nach einer ähnlichen Antwort mit mehr Fehlerbehandlung (einschließlich des Fehlers) suchen, würde ich vorschlagen, dass Daves stattdessen antwortet. –

0

Ich adaptiere die Antwort von dave und füge eine rekursive Funktion hinzu. Ich arbeite immer noch an einer benutzerfreundlicheren Version. Zum Beispiel sollte eine Zahlenfolge 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) 
} 
36

Hashicorp des https://github.com/mitchellh/mapstructure Bibliothek tut dies aus der Box:

import "github.com/mitchellh/mapstructure" 

mapstructure.Decode(myData, &result) 

Die zweiten result Parameter haben eine Adresse der Struktur sein.

+3

Das ist großartig, danke.Kein Punkt, es selbst zu implementieren – Brian

+3

Noch ein Dankeschön! – shapeshifter

Verwandte Themen