diff --git a/structure.go b/structure.go new file mode 100644 index 0000000..634beb0 --- /dev/null +++ b/structure.go @@ -0,0 +1,51 @@ +// Package structure contains various utilities functions to work with structs. +package structure + +import ( + "errors" + "reflect" +) + +// ErrNotStruct is returned when the passed value is not a struct +var ErrNotStruct = errors.New("not struct") + +// ToMap converts a struct to a map[string]interface{}. The default map key +// string is the struct fieldname but this can be changed by defining a +// "structure" tag key if needed. Note that only exported fields of a struct +// can be accessed, non exported fields will be neglected. +func ToMap(in interface{}) (map[string]interface{}, error) { + out := make(map[string]interface{}) + + t := reflect.TypeOf(in) + + // if pointer get the underlying element≤ + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + + if t.Kind() != reflect.Struct { + return nil, ErrNotStruct + } + + v := reflect.ValueOf(in) + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + // we can't access the value of unexported fields + if field.PkgPath != "" { + continue + } + + name := field.Name + + // override if the user passed a structure tag + if tag := field.Tag.Get("structure"); tag != "" { + name = tag + } + + out[name] = v.Field(i).Interface() + } + + return out, nil +} diff --git a/structure_test.go b/structure_test.go new file mode 100644 index 0000000..6234847 --- /dev/null +++ b/structure_test.go @@ -0,0 +1,84 @@ +package structure + +import ( + "reflect" + "testing" +) + +func TestToMap(t *testing.T) { + var T = struct { + A string + B int + C bool + }{ + A: "a-value", + B: 2, + C: true, + } + + a, err := ToMap(T) + if err != nil { + t.Error(err) + } + + if typ := reflect.TypeOf(a).Kind(); typ != reflect.Map { + t.Errorf("ToMap should return a map type, got: %v", typ) + } + + // we have three fields + if len(a) != 3 { + t.Errorf("ToMap should return a map of len 3, got: %d", len(a)) + } + + inMap := func(val interface{}) bool { + for _, v := range a { + if reflect.DeepEqual(v, val) { + return true + } + } + + return false + } + + for _, val := range []interface{}{"a-value", 2, true} { + if !inMap(val) { + t.Errorf("ToMap should have the value %v", val) + } + } + +} + +func TestToMap_Tag(t *testing.T) { + var T = struct { + A string `structure:"x"` + B int `structure:"y"` + C bool `structure:"z"` + }{ + A: "a-value", + B: 2, + C: true, + } + + a, err := ToMap(T) + if err != nil { + t.Error(err) + } + + inMap := func(key interface{}) bool { + for k := range a { + if reflect.DeepEqual(k, key) { + + return true + } + } + + return false + } + + for _, key := range []string{"x", "y", "z"} { + if !inMap(key) { + t.Errorf("ToMap should have the key %v", key) + } + } + +}