In golang, a generic function for loading http form data into a structure

In Go, http-form data (for example, from a POST or PUT request) can be obtained as a map of the form map[string][]string . It is difficult for me to convert this into structures in a generalized way.

For example, I want to download a map such as:

 m := map[string][]string { "Age": []string{"20"}, "Name": []string{"John Smith"}, } 

In a model, for example:

 type Person struct { Age int Name string } 

So, I'm trying to write a function with the signature LoadModel(obj interface{}, m map[string][]string) []error , which will load the form data into the interface {}, which I can print to return to the character. Using reflection, so that I can use it for any type of structure with any fields, not just Face, and so that I can convert a string from http data to int, boolean, etc. As needed.

Using the answer to this question in golang, using reflection, how do you set the value of a struct field? I can set the value of a person using reflection, for example:

 p := Person{25, "John"} reflect.ValueOf(&p).Elem().Field(1).SetString("Dave") 

But then I will have to copy the load function for each type of structure that I have. When I try to use it for the {} interface, it does not work.

 pi := (interface{})(p) reflect.ValueOf(&pi).Elem().Field(1).SetString("Dave") // panic: reflect: call of reflect.Value.Field on interface Value 

How can I do this in general? Or, better yet, is there a more idiomatic way to achieve what I'm trying to do?

+7
source share
3 answers

For pleasure I tried. Notice that I cheated a little (see Comments), but you should get a picture. It is usually worth using reflection against statically typed assignments (e.g. nemo answer), so be sure to weigh this in your solution (I have not tested it).

Besides the obvious disclaimer, I have not tested all cases with the edge, etc. etc. Don't just copy it into production code :)

So here it is:

 package main import ( "fmt" "reflect" "strconv" ) type Person struct { Age int Name string Salary float64 } // I cheated a little bit, made the map value a string instead of a slice. // Could've used just the index 0 instead, or fill an array of structs (obj). // Either way, this shows the reflection steps. // // Note: no error returned from this example, I just log to stdout. Could definitely // return an array of errors, and should catch a panic since this is possible // with the reflect package. func LoadModel(obj interface{}, m map[string]string) { defer func() { if e := recover(); e != nil { fmt.Printf("Panic! %v\n", e) } }() val := reflect.ValueOf(obj) if val.Kind() == reflect.Ptr { val = val.Elem() } // Loop over map, try to match the key to a field for k, v := range m { if f := val.FieldByName(k); f.IsValid() { // Is it assignable? if f.CanSet() { // Assign the map value to this field, converting to the right data type. switch f.Type().Kind() { // Only a few kinds, just to show the basic idea... case reflect.Int: if i, e := strconv.ParseInt(v, 0, 0); e == nil { f.SetInt(i) } else { fmt.Printf("Could not set int value of %s: %s\n", k, e) } case reflect.Float64: if fl, e := strconv.ParseFloat(v, 0); e == nil { f.SetFloat(fl) } else { fmt.Printf("Could not set float64 value of %s: %s\n", k, e) } case reflect.String: f.SetString(v) default: fmt.Printf("Unsupported format %v for field %s\n", f.Type().Kind(), k) } } else { fmt.Printf("Key '%s' cannot be set\n", k) } } else { // Key does not map to a field in obj fmt.Printf("Key '%s' does not have a corresponding field in obj %+v\n", k, obj) } } } func main() { m := map[string]string{ "Age": "36", "Name": "Johnny", "Salary": "1400.33", "Ignored": "True", } p := new(Person) LoadModel(p, m) fmt.Printf("After LoadModel: Person=%+v\n", p) } 
+8
source

You need to make keys for the general case and accordingly load different types of fields. This is the main part.

It becomes more difficult when you have fragments in the structure (then you need to load them up to the number of elements in the form field), or you have nested structures.

I wrote a package that does this. Please look:

http://www.gorillatoolkit.org/pkg/schema

+8
source

I would suggest using a specific interface in LoadModel instead of interface{} which your type must implement to load.

For example:

 type Loadable interface{ LoadValue(name string, value []string) } func LoadModel(loadable Loadable, data map[string][]string) { for key, value := range data { loadable.LoadValue(key, value) } } 

And your Person implements Loadable , implementing LoadModel as follows:

 type Person struct { Age int Name string } func (p *Person) LoadValue(name string, value []string) { switch name { case "Age": p.Age, err = strconv.Atoi(value[0]) // etc. } } 

Thus, the encoding/binary package or the encoding/json package works, for example.

+7
source

All Articles