Skip to content

Instantly share code, notes, and snippets.

@icholy
Created September 26, 2017 17:04
Show Gist options
  • Save icholy/abdb02657ef656524ce2a006ca776b8f to your computer and use it in GitHub Desktop.
Save icholy/abdb02657ef656524ce2a006ca776b8f to your computer and use it in GitHub Desktop.
package structmap
import (
"fmt"
"reflect"
"strings"
)
type Options struct {
Strict bool
NameMapper func(name string) string
}
func applyDefaults(o *Options) *Options {
if o == nil {
o = &Options{}
}
if o.NameMapper == nil {
o.NameMapper = normalizeName
}
return o
}
func MapToStruct(m map[string]interface{}, s interface{}, o *Options) error {
o = applyDefaults(o)
val := reflect.ValueOf(s).Elem()
fields := make(map[string]reflect.Value)
fieldMap(val, fields, o)
for name, value := range m {
field, ok := fields[o.NameMapper(name)]
if !ok {
if o.Strict {
return fmt.Errorf("missing field %s", name)
}
continue
}
if !field.CanSet() {
return fmt.Errorf("cannot set %s field value", name)
}
val := reflect.ValueOf(value)
if field.Type() != val.Type() {
return fmt.Errorf("invalid type for field %s", name)
}
field.Set(val)
}
return nil
}
func normalizeName(name string) string {
name = strings.Replace(name, "_", "", -1)
name = strings.Replace(name, "-", "", -1)
return strings.ToLower(name)
}
func fieldMap(s reflect.Value, m map[string]reflect.Value, o *Options) {
var embedded []reflect.Value
for i := 0; i < s.NumField(); i++ {
typ := s.Type().Field(i)
val := s.Field(i)
if typ.Anonymous {
embedded = append(embedded, val)
}
name := o.NameMapper(typ.Name)
if _, ok := m[name]; !ok {
m[name] = val
}
}
for _, val := range embedded {
fieldMap(val, m, o)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment