open-nomad/helper/flatmap/flatmap.go

128 lines
3.5 KiB
Go

package flatmap
import (
"fmt"
"reflect"
)
// Flatten takes an object and returns a flat map of the object. The keys of the
// map is the path of the field names until a primitive field is reached and the
// value is a string representation of the terminal field.
func Flatten(obj interface{}, filter []string, primitiveOnly bool) map[string]string {
flat := make(map[string]string)
v := reflect.ValueOf(obj)
if !v.IsValid() {
return nil
}
flatten("", v, primitiveOnly, false, flat)
for _, f := range filter {
delete(flat, f)
}
return flat
}
// flatten recursively calls itself to create a flatmap representation of the
// passed value. The results are stored into the output map and the keys are
// the fields prepended with the passed prefix.
// XXX: A current restriction is that maps only support string keys.
func flatten(prefix string, v reflect.Value, primitiveOnly, enteredStruct bool, output map[string]string) {
switch v.Kind() {
case reflect.Bool:
output[prefix] = fmt.Sprintf("%v", v.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
output[prefix] = fmt.Sprintf("%v", v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
output[prefix] = fmt.Sprintf("%v", v.Uint())
case reflect.Float32, reflect.Float64:
output[prefix] = fmt.Sprintf("%v", v.Float())
case reflect.Complex64, reflect.Complex128:
output[prefix] = fmt.Sprintf("%v", v.Complex())
case reflect.String:
output[prefix] = fmt.Sprintf("%v", v.String())
case reflect.Invalid:
output[prefix] = "nil"
case reflect.Ptr:
if primitiveOnly && enteredStruct {
return
}
e := v.Elem()
if !e.IsValid() {
output[prefix] = "nil"
}
flatten(prefix, e, primitiveOnly, enteredStruct, output)
case reflect.Map:
for _, k := range v.MapKeys() {
if k.Kind() == reflect.Interface {
k = k.Elem()
}
if k.Kind() != reflect.String {
panic(fmt.Sprintf("%q: map key is not string: %s", prefix, k))
}
flatten(getSubKeyPrefix(prefix, k.String()), v.MapIndex(k), primitiveOnly, enteredStruct, output)
}
case reflect.Struct:
if primitiveOnly && enteredStruct {
return
}
enteredStruct = true
t := v.Type()
for i := 0; i < v.NumField(); i++ {
name := t.Field(i).Name
val := v.Field(i)
if val.Kind() == reflect.Interface && !val.IsNil() {
val = val.Elem()
}
flatten(getSubPrefix(prefix, name), val, primitiveOnly, enteredStruct, output)
}
case reflect.Interface:
if primitiveOnly {
return
}
e := v.Elem()
if !e.IsValid() {
output[prefix] = "nil"
return
}
flatten(prefix, e, primitiveOnly, enteredStruct, output)
case reflect.Array, reflect.Slice:
if primitiveOnly {
return
}
if v.Kind() == reflect.Slice && v.IsNil() {
output[prefix] = "nil"
return
}
for i := 0; i < v.Len(); i++ {
flatten(fmt.Sprintf("%s[%d]", prefix, i), v.Index(i), primitiveOnly, enteredStruct, output)
}
default:
panic(fmt.Sprintf("prefix %q; unsupported type %v", prefix, v.Kind()))
}
}
// getSubPrefix takes the current prefix and the next subfield and returns an
// appropriate prefix.
func getSubPrefix(curPrefix, subField string) string {
if curPrefix != "" {
return fmt.Sprintf("%s.%s", curPrefix, subField)
}
return subField
}
// getSubKeyPrefix takes the current prefix and the next subfield and returns an
// appropriate prefix for a map field.
func getSubKeyPrefix(curPrefix, subField string) string {
if curPrefix != "" {
return fmt.Sprintf("%s[%s]", curPrefix, subField)
}
return subField
}