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  }