package reflectext import ( "reflect" "strings" ) // GetMapPath returns the value deep inside a hierahically nested map structure // eg: // x := langext.H{"K1": langext.H{"K2": 665}} // GetMapPath[int](x, "K1.K2") == 665 func GetMapPath[TData any](mapval any, path string) (TData, bool) { var ok bool split := strings.Split(path, ".") for i, key := range split { if i < len(split)-1 { mapval, ok = GetMapField[any](mapval, key) if !ok { return *new(TData), false } } else { return GetMapField[TData](mapval, key) } } return *new(TData), false } // GetMapField gets the value of a map, without knowing the actual types (mapval is any) // eg: // x := langext.H{"K1": 665} // GetMapPath[int](x, "K1") == 665 // // works with aliased types and autom. dereferences pointes func GetMapField[TData any, TKey comparable](mapval any, key TKey) (TData, bool) { rval := reflect.ValueOf(mapval) for rval.Kind() == reflect.Ptr && !rval.IsNil() { rval = rval.Elem() } if rval.Kind() != reflect.Map { return *new(TData), false // mapval is not a map } kval := reflect.ValueOf(key) if !kval.Type().AssignableTo(rval.Type().Key()) { return *new(TData), false // key cannot index mapval } eval := rval.MapIndex(kval) if !eval.IsValid() { return *new(TData), false // key does not exist in mapval } destType := reflect.TypeOf(new(TData)).Elem() if eval.Type() == destType { return eval.Interface().(TData), true } if eval.CanConvert(destType) && !preventConvert(eval.Type(), destType) { return eval.Convert(destType).Interface().(TData), true } if (eval.Kind() == reflect.Ptr || eval.Kind() == reflect.Interface) && eval.IsNil() && destType.Kind() == reflect.Ptr { return *new(TData), false // special case: mapval[key] is nil } for (eval.Kind() == reflect.Ptr || eval.Kind() == reflect.Interface) && !eval.IsNil() { eval = eval.Elem() if eval.Type() == destType { return eval.Interface().(TData), true } if eval.CanConvert(destType) && !preventConvert(eval.Type(), destType) { return eval.Convert(destType).Interface().(TData), true } } return *new(TData), false // mapval[key] is not of type TData } func preventConvert(t1 reflect.Type, t2 reflect.Type) bool { if t1.Kind() == reflect.String && t1.Kind() != reflect.String { return true } if t2.Kind() == reflect.String && t1.Kind() != reflect.String { return true } return false }