package forms import ( "encoding" "reflect" "strconv" "strings" "unicode" ) type DecodeError struct { Field string Type string Value string } func (de DecodeError) Error() string { return `Failed to decode "` + de.Value + `" into field ` + de.Field + ` of type ` + de.Type + `.` } type Values = map[string][]string func Decode(form Values, v interface{}) error { rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr { panic(`Input is not a pointer`) } return decode(form, rv.Elem(), ``) } func decode(form Values, rv reflect.Value, prefix string) error { for i := 0; i < rv.NumField(); i++ { ft, fv := rv.Type().Field(i), rv.Field(i) if !unicode.IsUpper(rune(ft.Name[0])) { continue } fe := ft.Type if fe.Kind() == reflect.Ptr { fe = fe.Elem() } if tu, ok := fv.Addr().Interface().(encoding.TextUnmarshaler); ok { v, ok := form[getName(ft, prefix)] if !ok || len(v) == 0 { continue } err := tu.UnmarshalText([]byte(v[0])) if err != nil { return err } continue } if fe.Kind() == reflect.Struct { if fv.Kind() == reflect.Ptr { found := false for k, _ := range form { if strings.HasPrefix(k, getPrefix(ft, prefix)) { found = true break } } if !found { continue } fv.Set(reflect.New(ft.Type.Elem())) fv = fv.Elem() } err := decode(form, fv, getPrefix(ft, prefix)) if err != nil { return err } continue } fieldName := getName(ft, prefix) v, ok := form[fieldName] if !ok || len(v) == 0 { continue } if fv.Kind() != reflect.Ptr { fv = fv.Addr() } else if fv.IsNil() { fv.Set(reflect.New(ft.Type.Elem())) } err := setValue(fieldName, v, fv) if err != nil { return err } } return nil } func setValue(fieldName string, values []string, fv reflect.Value) error { if fv.Elem().Type().Kind() != reflect.Slice { return parse(fieldName, values[0], fv) } slice := reflect.MakeSlice(fv.Elem().Type(), len(values), len(values)) val := reflect.New(slice.Type().Elem()) for i, v := range values { err := parse(fieldName, v, val) if err != nil { return err } slice.Index(i).Set(val.Elem()) } fv.Elem().Set(slice) return nil } func parse(fieldName, v string, fv reflect.Value) error { var err error switch f := fv.Interface().(type) { case *string: *f = v case *int: *f, err = strconv.Atoi(v) case *float32: var f64 float64 f64, err = strconv.ParseFloat(v, 32) *f = float32(f64) case *float64: *f, err = strconv.ParseFloat(v, 64) } if err != nil { return DecodeError{fieldName, fv.Elem().Type().Kind().String(), v} } return nil } func getPrefix(ft reflect.StructField, prefix string) string { if ft.Anonymous { return prefix } if prefix != `` { prefix += `.` } return prefix + ft.Name } func getName(ft reflect.StructField, prefix string) string { name := ft.Tag.Get(`form`) if name == `` { name = ft.Name } if prefix == `` { return name } return prefix + `.` + name }