Skip to content

Instantly share code, notes, and snippets.

@debedb
Created November 16, 2016 19:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save debedb/752c49c0cd0c73fa24056eed4bd2c6e4 to your computer and use it in GitHub Desktop.
Save debedb/752c49c0cd0c73fa24056eed4bd2c6e4 to your computer and use it in GitHub Desktop.
formMarshaller provides functionality to marshal/unmarshal data to/from HTML form format.
// 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