|
package main |
|
|
|
import ( |
|
"fmt" |
|
"log" |
|
"reflect" |
|
"strconv" |
|
) |
|
|
|
const ( |
|
prefix = "router.deis.io/" |
|
tag = "router" |
|
) |
|
|
|
type Foo struct { |
|
A string `router:"a"` |
|
B int `router:"b"` |
|
C *Bar `router:"c"` |
|
} |
|
|
|
type Bar struct { |
|
D string `router:"d"` |
|
E bool `router:"e"` |
|
} |
|
|
|
func main() { |
|
data := make(map[string]string) |
|
data[prefix+"a"] = "ABBA" |
|
data[prefix+"b"] = "5" |
|
data[prefix+"c.d"] = "DDD" |
|
data[prefix+"c.e"] = "true" |
|
foo := &Foo{C: &Bar{}} |
|
err := FillStruct(data, foo) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
fmt.Println(foo.A) |
|
fmt.Println(foo.B) |
|
fmt.Println(foo.C.D) |
|
fmt.Println(foo.C.E) |
|
} |
|
|
|
func FillStruct(data map[string]string, out interface{}) error { |
|
rv := reflect.ValueOf(out) |
|
return fillStruct(data, "", rv) |
|
} |
|
|
|
func fillStruct(data map[string]string, context string, rv reflect.Value) error { |
|
// If rv is invalid (represents a nil litersl), we cannot proceed. |
|
if rv.Kind() == reflect.Invalid { |
|
return fmt.Errorf("Cannot populate a nil from the map.") |
|
} |
|
// We also cannot proceed if rv is not a pointer. |
|
if rv.Kind() != reflect.Ptr { |
|
return fmt.Errorf("Cannot populate non-pointer type %s from the map.", rv.Type()) |
|
} |
|
// Or if it is a pointer to a nil. |
|
if rv.IsNil() { |
|
return fmt.Errorf("Cannot populate nil %s from the map.", rv.Type()) |
|
} |
|
// At this point, we know we're working with a pointer that points to something. |
|
// Get what it points to... |
|
elem := rv.Elem() |
|
// If the thing it points to isn't a struct, that's also a problem. |
|
if elem.Kind() != reflect.Struct { |
|
return fmt.Errorf("Cannot populate non-struct-pointer type %s from the map.", rv.Type()) |
|
} |
|
rt := elem.Type() |
|
for i := 0; i < rt.NumField(); i++ { |
|
rf := rt.Field(i) |
|
tagValue := rf.Tag.Get(tag) |
|
// If no tag value is found... |
|
if tagValue == "" { |
|
// Just move to the next field. |
|
continue |
|
} |
|
if rf.Type.Kind() == reflect.Ptr || rf.Type.Kind() == reflect.Struct { |
|
// We're nested... use some recursion... |
|
var nestedContext string |
|
if context == "" { |
|
nestedContext = tagValue |
|
} else { |
|
nestedContext = fmt.Sprintf("%s.%s", context, tagValue) |
|
} |
|
err := fillStruct(data, nestedContext, elem.Field(i)) |
|
if err != nil { |
|
return err |
|
} |
|
} else { |
|
// We're not nested! |
|
var key string |
|
if context == "" { |
|
key = fmt.Sprintf("%s%s", prefix, tagValue) |
|
} else { |
|
key = fmt.Sprintf("%s%s.%s", prefix, context, tagValue) |
|
} |
|
stringVal, ok := data[key] |
|
if ok { |
|
if rf.Type.Name() == "string" { |
|
elem.Field(i).Set(reflect.ValueOf(stringVal)) |
|
} else if rf.Type.Name() == "int" { |
|
intVal, err := strconv.Atoi(stringVal) |
|
if err != nil { |
|
return err |
|
} |
|
elem.Field(i).Set(reflect.ValueOf(intVal)) |
|
} else if rf.Type.Name() == "bool" { |
|
boolVal, err := strconv.ParseBool(stringVal) |
|
if err != nil { |
|
return err |
|
} |
|
elem.Field(i).Set(reflect.ValueOf(boolVal)) |
|
} |
|
} |
|
} |
|
} |
|
return nil |
|
} |