package maputil import ( "reflect" "strconv" "strings" "github.com/gookit/goutil/comdef" "github.com/gookit/goutil/reflects" ) // some consts for separators const ( Wildcard = "*" PathSep = "." ) // DeepGet value by key path. eg "top" "top.sub" func DeepGet(mp map[string]any, path string) (val any) { val, _ = GetByPath(path, mp) return } // QuietGet value by key path. eg "top" "top.sub" func QuietGet(mp map[string]any, path string) (val any) { val, _ = GetByPath(path, mp) return } // GetFromAny get value by key path from any(map,slice) data. eg "top" "top.sub" func GetFromAny(path string, data any) (val any, ok bool) { // empty data if data == nil { return nil, false } if len(path) == 0 { return data, true } return getByPathKeys(data, strings.Split(path, ".")) } // GetByPath get value by key path from a map(map[string]any). eg "top" "top.sub" func GetByPath(path string, mp map[string]any) (val any, ok bool) { if len(path) == 0 { return mp, true } if val, ok := mp[path]; ok { return val, true } // no sub key if len(mp) == 0 || strings.IndexByte(path, '.') < 1 { return nil, false } // key is path. eg: "top.sub" return GetByPathKeys(mp, strings.Split(path, ".")) } // GetByPathKeys get value by path keys from a map(map[string]any). eg "top" "top.sub" // // Example: // // mp := map[string]any{ // "top": map[string]any{ // "sub": "value", // }, // } // val, ok := GetByPathKeys(mp, []string{"top", "sub"}) // return "value", true func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) { kl := len(keys) if kl == 0 { return mp, true } // find top item data use top key var item any topK := keys[0] if item, ok = mp[topK]; !ok { return } // find sub item data use sub key return getByPathKeys(item, keys[1:]) } func getByPathKeys(item any, keys []string) (val any, ok bool) { kl := len(keys) for i, k := range keys { switch tData := item.(type) { case map[string]string: // is string map if item, ok = tData[k]; !ok { return } case map[string]any: // is map(decode from toml/json/yaml) if item, ok = tData[k]; !ok { return } case map[any]any: // is map(decode from yaml.v2) if item, ok = tData[k]; !ok { return } case []map[string]any: // is an any-map slice if k == Wildcard { if kl == i+1 { // * is last key return tData, true } // * is not last key, find sub item data sl := make([]any, 0, len(tData)) for _, v := range tData { if val, ok = getByPathKeys(v, keys[i+1:]); ok { sl = append(sl, val) } } if len(sl) > 0 { return sl, true } return nil, false } // k is index number idx, err := strconv.Atoi(k) if err != nil || idx >= len(tData) { return nil, false } item = tData[idx] default: if k == Wildcard && kl == i+1 { // * is last key return tData, true } rv := reflect.ValueOf(tData) // check is slice if rv.Kind() == reflect.Slice { if k == Wildcard { // * is not last key, find sub item data sl := make([]any, 0, rv.Len()) for si := 0; si < rv.Len(); si++ { el := reflects.Indirect(rv.Index(si)) if el.Kind() != reflect.Map { return nil, false } // el is map value. if val, ok = getByPathKeys(el.Interface(), keys[i+1:]); ok { sl = append(sl, val) } } if len(sl) > 0 { return sl, true } return nil, false } // check k is index number ii, err := strconv.Atoi(k) if err != nil || ii >= rv.Len() { return nil, false } item = rv.Index(ii).Interface() continue } // as error return nil, false } // next is last key and it is * if kl == i+2 && keys[i+1] == Wildcard { return item, true } } return item, true } // Keys get all keys of the given map. func Keys(mp any) (keys []string) { rftVal := reflect.Indirect(reflect.ValueOf(mp)) if rftVal.Kind() != reflect.Map { return } keys = make([]string, 0, rftVal.Len()) for _, key := range rftVal.MapKeys() { keys = append(keys, key.String()) } return } // TypedKeys get all keys of the given typed map. func TypedKeys[K comdef.SimpleType, V any](mp map[K]V) (keys []K) { for key := range mp { keys = append(keys, key) } return } // FirstKey returns the first key of the given map. func FirstKey[T any](mp map[string]T) string { for key := range mp { return key } return "" } // Values get all values from the given map. func Values(mp any) (values []any) { rv := reflect.Indirect(reflect.ValueOf(mp)) if rv.Kind() != reflect.Map { return } values = make([]any, 0, rv.Len()) for _, key := range rv.MapKeys() { values = append(values, rv.MapIndex(key).Interface()) } return } // TypedValues get all values from the given typed map. func TypedValues[K comdef.SimpleType, V any](mp map[K]V) (values []V) { for _, val := range mp { values = append(values, val) } return } // EachAnyMap iterates the given map and calls the given function for each item. func EachAnyMap(mp any, fn func(key string, val any)) { rv := reflect.Indirect(reflect.ValueOf(mp)) if rv.Kind() != reflect.Map { panic("not a map value") } for _, key := range rv.MapKeys() { fn(key.String(), rv.MapIndex(key).Interface()) } } // EachTypedMap iterates the given map and calls the given function for each item. func EachTypedMap[K comdef.SimpleType, V any](mp map[K]V, fn func(key K, val V)) { for key, val := range mp { fn(key, val) } }