github.com/pluralsh/plural-cli@v0.9.5/pkg/utils/map.go (about) 1 package utils 2 3 import ( 4 "fmt" 5 "reflect" 6 7 jsonpatch "github.com/evanphx/json-patch" 8 "github.com/google/go-cmp/cmp" 9 jsoniter "github.com/json-iterator/go" 10 "golang.org/x/exp/maps" 11 "k8s.io/apimachinery/pkg/util/sets" 12 ) 13 14 var json = jsoniter.ConfigCompatibleWithStandardLibrary 15 16 func CleanUpInterfaceMap(in map[interface{}]interface{}) map[string]interface{} { 17 result := make(map[string]interface{}) 18 for k, v := range in { 19 result[fmt.Sprintf("%v", k)] = cleanUpMapValue(v) 20 } 21 return result 22 } 23 24 func RemoveNulls(m map[string]interface{}) { 25 val := reflect.ValueOf(m) 26 for _, e := range val.MapKeys() { 27 v := val.MapIndex(e) 28 if v.IsNil() { 29 delete(m, e.String()) 30 continue 31 } 32 33 t, ok := v.Interface().(map[string]interface{}) 34 if ok { 35 RemoveNulls(t) 36 } 37 // if the map is empty, remove it 38 // TODO: add a unit test for this 39 if ok && len(t) == 0 { 40 delete(m, e.String()) 41 } 42 } 43 } 44 45 func PatchInterfaceMap(defaultValues, values map[string]map[string]interface{}) (map[string]map[string]interface{}, error) { 46 defaultJson, err := json.Marshal(defaultValues) 47 if err != nil { 48 return nil, err 49 } 50 valuesJson, err := json.Marshal(values) 51 if err != nil { 52 return nil, err 53 } 54 55 patchJson, err := jsonpatch.CreateMergePatch(defaultJson, valuesJson) 56 if err != nil { 57 return nil, err 58 } 59 60 patch := map[string]map[string]interface{}{} 61 if err := json.Unmarshal(patchJson, &patch); err != nil { 62 return nil, err 63 } 64 for key := range patch { 65 // if the map is empty, remove it 66 if len(patch[key]) == 0 { 67 delete(patch, key) 68 } else { 69 // remove nulls from the map 70 RemoveNulls(patch[key]) 71 // if the map is empty after removing nulls, remove it 72 if len(patch[key]) == 0 { 73 delete(patch, key) 74 } 75 } 76 } 77 // if the patch is empty, return an empty map 78 if len(patch) == 0 { 79 return map[string]map[string]interface{}{}, nil 80 } 81 return patch, nil 82 } 83 84 type DiffCondition func(key string, value, diffValue any) bool 85 86 var ( 87 equalDiffCondition DiffCondition = func(_ string, value, diffValue any) bool { 88 return cmp.Equal(value, diffValue) 89 } 90 ) 91 92 // DiffMap removes keys from the base map based on provided DiffCondition match against the same keys in provided 93 // diff map. It always uses an equal comparison for the values, but specific keys can use extended comparison 94 // if needed by passing custom DiffCondition function. 95 // 96 // Example: 97 // 98 // A: {a: 1, b: 1, c: 2} 99 // B: {a: 1, d: 2, c: 3} 100 // Result: {b: 1, c: 2} 101 // 102 // Note: It does not remove null value keys by default. 103 func DiffMap(base, diff map[string]interface{}, conditions ...DiffCondition) map[string]interface{} { 104 result := make(map[string]interface{}) 105 maps.Copy(result, base) 106 107 if diff == nil { 108 diff = make(map[string]interface{}) 109 } 110 111 for k, v := range base { 112 switch t := v.(type) { 113 case map[string]interface{}: 114 dValue, _ := diff[k].(map[string]interface{}) 115 if dMap := DiffMap(t, dValue, conditions...); len(dMap) > 0 { 116 result[k] = dMap 117 break 118 } 119 120 delete(result, k) 121 default: 122 diffV := diff[k] 123 for _, condition := range append(conditions, equalDiffCondition) { 124 if condition(k, v, diffV) { 125 delete(result, k) 126 break 127 } 128 } 129 } 130 } 131 132 return result 133 } 134 135 func cleanUpInterfaceArray(in []interface{}) []interface{} { 136 result := make([]interface{}, len(in)) 137 for i, v := range in { 138 result[i] = cleanUpMapValue(v) 139 } 140 return result 141 } 142 143 func cleanUpMapValue(v interface{}) interface{} { 144 switch v := v.(type) { 145 case []interface{}: 146 return cleanUpInterfaceArray(v) 147 case map[interface{}]interface{}: 148 return CleanUpInterfaceMap(v) 149 case string: 150 return v 151 case bool: 152 return v 153 case int: 154 return v 155 default: 156 return fmt.Sprintf("%v", v) 157 } 158 } 159 160 func Dedupe(l []string) []string { 161 return sets.NewString(l...).List() 162 } 163 164 type SimpleType interface { 165 string | int 166 } 167 168 func Map[T any, R SimpleType](slice []T, mapper func(elem T) R) []R { 169 res := make([]R, 0) 170 171 for _, elem := range slice { 172 res = append(res, mapper(elem)) 173 } 174 175 return res 176 }