package reflectext import ( "reflect" ) var reflectBasicTypes = map[reflect.Kind]reflect.Type{ reflect.Bool: reflect.TypeFor[bool](), reflect.Int: reflect.TypeFor[int](), reflect.Int8: reflect.TypeFor[int8](), reflect.Int16: reflect.TypeFor[int16](), reflect.Int32: reflect.TypeFor[int32](), reflect.Int64: reflect.TypeFor[int64](), reflect.Uint: reflect.TypeFor[uint](), reflect.Uint8: reflect.TypeFor[uint8](), reflect.Uint16: reflect.TypeFor[uint16](), reflect.Uint32: reflect.TypeFor[uint32](), reflect.Uint64: reflect.TypeFor[uint64](), reflect.Uintptr: reflect.TypeFor[uintptr](), reflect.Float32: reflect.TypeFor[float32](), reflect.Float64: reflect.TypeFor[float64](), reflect.Complex64: reflect.TypeFor[complex64](), reflect.Complex128: reflect.TypeFor[complex128](), reflect.String: reflect.TypeFor[string](), } // Underlying returns the underlying type of t (without type alias) // // https://github.com/golang/go/issues/39574#issuecomment-655664772 func Underlying(t reflect.Type) (ret reflect.Type) { if t.Name() == "" { // t is an unnamed type. the underlying type is t itself return t } kind := t.Kind() if ret = reflectBasicTypes[kind]; ret != nil { return ret } switch kind { case reflect.Array: ret = reflect.ArrayOf(t.Len(), t.Elem()) case reflect.Chan: ret = reflect.ChanOf(t.ChanDir(), t.Elem()) case reflect.Map: ret = reflect.MapOf(t.Key(), t.Elem()) case reflect.Func: nIn := t.NumIn() nOut := t.NumOut() in := make([]reflect.Type, nIn) out := make([]reflect.Type, nOut) for i := range nIn { in[i] = t.In(i) } for i := range nOut { out[i] = t.Out(i) } ret = reflect.FuncOf(in, out, t.IsVariadic()) case reflect.Interface: // not supported case reflect.Pointer: ret = reflect.PointerTo(t.Elem()) case reflect.Slice: ret = reflect.SliceOf(t.Elem()) case reflect.Struct: // only partially supported: embedded fields // and unexported fields may cause panic in reflect.StructOf() defer func() { // if a panic happens, return t unmodified if recover() != nil && ret == nil { ret = t } }() n := t.NumField() fields := make([]reflect.StructField, n) for i := range n { fields[i] = t.Field(i) } ret = reflect.StructOf(fields) } return ret } // TryCast works similar to `v2, ok := v.(T)` // Except it works through type alias' func TryCast[T any](v any) (T, bool) { underlying := Underlying(reflect.TypeOf(v)) def := *new(T) if underlying != Underlying(reflect.TypeOf(def)) { return def, false } r1 := reflect.ValueOf(v) if !r1.CanConvert(underlying) { return def, false } r2 := r1.Convert(underlying) r3 := r2.Interface() r4, ok := r3.(T) if !ok { return def, false } return r4, true } func TryCastType(v any, dest reflect.Type) (any, bool) { underlying := Underlying(reflect.TypeOf(v)) if underlying != Underlying(dest) { return nil, false } r1 := reflect.ValueOf(v) if !r1.CanConvert(underlying) { return nil, false } r2 := r1.Convert(underlying) if !r2.CanConvert(dest) { return nil, false } r4 := r2.Convert(dest) return r4.Interface(), true }