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