Created
November 16, 2016 19:39
-
-
Save debedb/752c49c0cd0c73fa24056eed4bd2c6e4 to your computer and use it in GitHub Desktop.
formMarshaller provides functionality to marshal/unmarshal data to/from HTML form format.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// formMarshaller provides functionality to marshal/unmarshal | |
// data to/from HTML form format. | |
type formMarshaller struct{} | |
func (j formMarshaller) Marshal(v interface{}) ([]byte, error) { | |
retval := "" | |
vPtr := reflect.ValueOf(v) | |
vVal := vPtr.Elem() | |
vType := reflect.TypeOf(vVal.Interface()) | |
for i := 0; i < vVal.NumField(); i++ { | |
metaField := vType.Field(i) | |
field := vVal.Field(i) | |
formKey := metaField.Tag.Get("form") | |
if len(retval) > 0 { | |
retval += "&" | |
} | |
retval += formKey + "=" | |
log.Printf("form key of %s is %s\n", metaField.Name, formKey) | |
str := "" | |
if metaField.Type == stringType { | |
str = field.Interface().(string) | |
} else { | |
toString := field.MethodByName("String") | |
log.Printf("Looking for method String on %s: %s\n", field, toString) | |
if reflect.Zero(reflect.TypeOf(toString)) != toString { | |
toStringResult := toString.Call(nil) | |
str = toStringResult[0].String() | |
} else { | |
log.Printf("Ignoring field %s of %s\n", metaField.Name, v) | |
continue | |
} | |
} | |
str = strings.TrimSpace(str) | |
retval += str | |
} | |
return []byte(retval), nil | |
} | |
// Unmarshal attempts to take a payload of an HTML form | |
// (key=value pairs separated by &, application/x-www-form-urlencoded | |
// MIME) and fill the v structure from it. It is not a universal method, | |
// and right now is limited to this simple functionality: | |
// 1. No support for multiple values for the same key (though HTML forms allow it). | |
// 2. interface v must be one of: | |
// a. map[string]interface{} | |
// b. Contain string fields for every field in the form OR, | |
// implement a Set<Field> method. (Structure tag "form" can be | |
// used to map the form key to the structure field if they are | |
// different). Here is a supported example: | |
// type NetIf struct { | |
// Mac string `form:"mac_address"` // Will get set because it's a string. | |
// IP net.IP `form:"ip_address"` // Will get set because of SetIP() method below. | |
// } | |
// | |
//func (netif *NetIf) SetIP(ip string) error { | |
// netif.IP = net.ParseIP(ip) | |
// if netif.IP == nil { | |
// return failedToParseNetif() | |
// } | |
// return nil | |
//} | |
func (f formMarshaller) Unmarshal(data []byte, v interface{}) error { | |
log.Printf("Entering formMarshaller.Unmarshal()\n") | |
var err error | |
dataStr := string(data) | |
// We'll keep it simple - make a map and use mapstructure | |
vPtr := reflect.ValueOf(v) | |
vVal := vPtr.Elem() | |
vType := reflect.TypeOf(vVal.Interface()) | |
kvPairs := strings.Split(dataStr, "&") | |
var m map[string]interface{} | |
if vType.Kind() == reflect.Map { | |
// If the output wanted is a map, then just use it as a map. | |
m = *(v.(*map[string]interface{})) | |
} else { | |
// Otherwise, first make a temporary map | |
m = make(map[string]interface{}) | |
} | |
for i := range kvPairs { | |
kv := strings.Split(kvPairs[i], "=") | |
// Of course we have to do checking etc... | |
key := string(kv[0]) | |
val := string(kv[1]) | |
val2, err := url.QueryUnescape(val) | |
if err != nil { | |
return err | |
} | |
m[key] = val2 | |
} | |
log.Printf("Unmarshaled form %s to map %s\n", dataStr, m) | |
if vType.Kind() == reflect.Map { | |
// At this point we already have filled in the map, | |
// and map is the type we want, so we return. | |
return nil | |
} | |
for i := 0; i < vVal.NumField(); i++ { | |
metaField := vType.Field(i) | |
field := vVal.Field(i) | |
formKey := metaField.Tag.Get("form") | |
formValue := m[formKey] | |
log.Printf("Value of %s is %s\n", metaField.Name, formValue) | |
if metaField.Type == stringType { | |
field.SetString(formValue.(string)) | |
} else { | |
setterMethodName := fmt.Sprintf("Set%s", metaField.Name) | |
setterMethod := vPtr.MethodByName(setterMethodName) | |
log.Printf("Looking for method %s on %s: %s\n", setterMethodName, vPtr, setterMethod) | |
if reflect.Zero(reflect.TypeOf(setterMethod)) != setterMethod { | |
valueArg := reflect.ValueOf(formValue) | |
valueArgs := []reflect.Value{valueArg} | |
result := setterMethod.Call(valueArgs) | |
errIfc := result[0].Interface() | |
if errIfc != nil { | |
return errIfc.(error) | |
} | |
} else { | |
return fmt.Errorf("Unsupported type of field %s: %s", metaField.Name, metaField.Type) | |
} | |
} | |
} | |
return err | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment