github.com/bigcommerce/nomad@v0.9.3-bc/helper/flatmap/flatmap.go (about)

     1  package flatmap
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  )
     7  
     8  // Flatten takes an object and returns a flat map of the object. The keys of the
     9  // map is the path of the field names until a primitive field is reached and the
    10  // value is a string representation of the terminal field.
    11  func Flatten(obj interface{}, filter []string, primitiveOnly bool) map[string]string {
    12  	flat := make(map[string]string)
    13  	v := reflect.ValueOf(obj)
    14  	if !v.IsValid() {
    15  		return nil
    16  	}
    17  
    18  	flatten("", v, primitiveOnly, false, flat)
    19  	for _, f := range filter {
    20  		if _, ok := flat[f]; ok {
    21  			delete(flat, f)
    22  		}
    23  	}
    24  	return flat
    25  }
    26  
    27  // flatten recursively calls itself to create a flatmap representation of the
    28  // passed value. The results are stored into the output map and the keys are
    29  // the fields prepended with the passed prefix.
    30  // XXX: A current restriction is that maps only support string keys.
    31  func flatten(prefix string, v reflect.Value, primitiveOnly, enteredStruct bool, output map[string]string) {
    32  	switch v.Kind() {
    33  	case reflect.Bool:
    34  		output[prefix] = fmt.Sprintf("%v", v.Bool())
    35  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    36  		output[prefix] = fmt.Sprintf("%v", v.Int())
    37  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
    38  		output[prefix] = fmt.Sprintf("%v", v.Uint())
    39  	case reflect.Float32, reflect.Float64:
    40  		output[prefix] = fmt.Sprintf("%v", v.Float())
    41  	case reflect.Complex64, reflect.Complex128:
    42  		output[prefix] = fmt.Sprintf("%v", v.Complex())
    43  	case reflect.String:
    44  		output[prefix] = fmt.Sprintf("%v", v.String())
    45  	case reflect.Invalid:
    46  		output[prefix] = "nil"
    47  	case reflect.Ptr:
    48  		if primitiveOnly && enteredStruct {
    49  			return
    50  		}
    51  
    52  		e := v.Elem()
    53  		if !e.IsValid() {
    54  			output[prefix] = "nil"
    55  		}
    56  		flatten(prefix, e, primitiveOnly, enteredStruct, output)
    57  	case reflect.Map:
    58  		for _, k := range v.MapKeys() {
    59  			if k.Kind() == reflect.Interface {
    60  				k = k.Elem()
    61  			}
    62  
    63  			if k.Kind() != reflect.String {
    64  				panic(fmt.Sprintf("%q: map key is not string: %s", prefix, k))
    65  			}
    66  
    67  			flatten(getSubKeyPrefix(prefix, k.String()), v.MapIndex(k), primitiveOnly, enteredStruct, output)
    68  		}
    69  	case reflect.Struct:
    70  		if primitiveOnly && enteredStruct {
    71  			return
    72  		}
    73  		enteredStruct = true
    74  
    75  		t := v.Type()
    76  		for i := 0; i < v.NumField(); i++ {
    77  			name := t.Field(i).Name
    78  			val := v.Field(i)
    79  			if val.Kind() == reflect.Interface && !val.IsNil() {
    80  				val = val.Elem()
    81  			}
    82  
    83  			flatten(getSubPrefix(prefix, name), val, primitiveOnly, enteredStruct, output)
    84  		}
    85  	case reflect.Interface:
    86  		if primitiveOnly {
    87  			return
    88  		}
    89  
    90  		e := v.Elem()
    91  		if !e.IsValid() {
    92  			output[prefix] = "nil"
    93  			return
    94  		}
    95  		flatten(prefix, e, primitiveOnly, enteredStruct, output)
    96  	case reflect.Array, reflect.Slice:
    97  		if primitiveOnly {
    98  			return
    99  		}
   100  
   101  		if v.Kind() == reflect.Slice && v.IsNil() {
   102  			output[prefix] = "nil"
   103  			return
   104  		}
   105  		for i := 0; i < v.Len(); i++ {
   106  			flatten(fmt.Sprintf("%s[%d]", prefix, i), v.Index(i), primitiveOnly, enteredStruct, output)
   107  		}
   108  	default:
   109  		panic(fmt.Sprintf("prefix %q; unsupported type %v", prefix, v.Kind()))
   110  	}
   111  }
   112  
   113  // getSubPrefix takes the current prefix and the next subfield and returns an
   114  // appropriate prefix.
   115  func getSubPrefix(curPrefix, subField string) string {
   116  	if curPrefix != "" {
   117  		return fmt.Sprintf("%s.%s", curPrefix, subField)
   118  	}
   119  	return fmt.Sprintf("%s", subField)
   120  }
   121  
   122  // getSubKeyPrefix takes the current prefix and the next subfield and returns an
   123  // appropriate prefix for a map field.
   124  func getSubKeyPrefix(curPrefix, subField string) string {
   125  	if curPrefix != "" {
   126  		return fmt.Sprintf("%s[%s]", curPrefix, subField)
   127  	}
   128  	return fmt.Sprintf("%s", subField)
   129  }