You may use Type.Elem()
to get the type's element type, which works for Array
, Chan
, Map
, Ptr
and Slice
.
You may run a loop and "navigate" to the type's element type until the type is not a pointer nor a slice (nor array, chan, map if you need so).
So the simple solution is this:
func getElemType(a interface{}) reflect.Type {
for t := reflect.TypeOf(a); ; {
switch t.Kind() {
case reflect.Ptr, reflect.Slice:
t = t.Elem()
default:
return t
}
}
}
Testing it:
type MyModel struct{}
fmt.Println(getElemType(MyModel{}))
fmt.Println(getElemType(&MyModel{}))
fmt.Println(getElemType([]MyModel{}))
fmt.Println(getElemType([]*MyModel{}))
fmt.Println(getElemType(&[]*MyModel{}))
fmt.Println(getElemType(&[]****MyModel{}))
fmt.Println(getElemType(&[][]**[]*[]***MyModel{}))
var p *[][]**[]*[]***MyModel
fmt.Println(p) // It's nil!
fmt.Println(getElemType(p))
Output (try it on the Go Playground):
main.MyModel
main.MyModel
main.MyModel
main.MyModel
main.MyModel
main.MyModel
main.MyModel
<nil>
main.MyModel
As you can see, no matter how "deep" we go with slices and pointers (&[][]**[]*[]***MyModel{}
), getElemType()
is able to extract main.MyModel
.
One thing to note is that in my solution I used reflect.Type
and not reflect.Value
. Go is a statically typed language, so the type information is there even if pointers and slice elements are not "populated", even if we pass a "typed" nil
such as p
, we're still able to navigate through the "type chain".
Note: The above getElemType()
panics if called with an untyped nil
value, e.g. getElemType(nil)
, because in this case there is no type information available. To defend this, you may add a simple check:
if a == nil {
return nil
}
Note #2: Since the implementation contains a loop without limiting iteration count, values of recursive types will drive it into an endless loop, such as:
type RecType []RecType
getElemType(RecType{}) // Endless loop!