How to find the type of an object in Go?

Learn how to find the type of an object in go? with practical examples, diagrams, and best practices. Covers go, go-reflect development techniques with visual explanations.

How to Determine the Type of an Object in Go

Hero image for How to find the type of an object in Go?

Learn various methods to identify the type of a variable or interface in Go, leveraging built-in features and the reflect package for dynamic type inspection.

Understanding the type of a variable is fundamental in any programming language. In Go, a statically typed language, types are usually known at compile time. However, there are scenarios, especially when working with interfaces or dynamic data, where you need to determine an object's type at runtime. Go provides several mechanisms to achieve this, ranging from simple type assertions to the powerful reflect package.

Basic Type Checking: Type Assertions and Type Switches

For variables whose underlying type is known to be one of a few possibilities, Go's type assertion and type switch mechanisms are the most straightforward and idiomatic ways to determine the type. These methods are compile-time safe and efficient.

package main

import (
	"fmt"
)

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

func main() {
	var i interface{} = "hello"

	s, ok := i.(string)
	fmt.Println(s, ok) // Output: hello true

	f, ok := i.(float64)
	fmt.Println(f, ok) // Output: 0 false

	i = 42
	switchedType(i)

	i = "world"
	switchedType(i)

	i = true
	switchedType(i)
}

func switchedType(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Integer: %d\n", v)
	case string:
		fmt.Printf("String: %s\n", v)
	default:
		fmt.Printf("Unknown type: %T\n", v)
	}
}

Using type assertions and type switches for basic type checking.

Dynamic Type Inspection with the reflect Package

When you need to inspect types dynamically, especially when dealing with arbitrary data structures or when the possible types are not known in advance, Go's reflect package is indispensable. The reflect package provides functions to inspect the type and value of any variable at runtime. It's particularly useful for serialization, ORMs, and generic programming.

flowchart TD
    A[Input: interface{}] --> B{reflect.TypeOf(i)}
    B --> C{reflect.ValueOf(i)}
    C --> D{Value.Kind()}
    C --> E{Value.Type()}
    D --> F[Basic Type (e.g., int, string, struct)]
    E --> G[Full Type (e.g., *main.MyStruct, []int)]
    F --> H{Perform actions based on Kind}
    G --> I{Perform actions based on Type}
    H --> J[Output: Dynamic Behavior]
    I --> J

Flowchart illustrating the use of reflect.TypeOf and reflect.ValueOf.

The reflect package offers two main types for introspection: reflect.Type and reflect.Value.

  • reflect.TypeOf(i interface{}) Type: Returns the reflect.Type that represents the dynamic type of the interface value i. This type describes the static type of the variable.
  • reflect.ValueOf(i interface{}) Value: Returns the reflect.Value that represents the dynamic value of the interface value i. This value allows manipulation of the underlying data.

From these, you can extract information like the Kind (the underlying type category, e.g., int, string, struct, ptr, slice) and the Name (the type's name within its package).

package main

import (
	"fmt"
	"reflect"
)

type MyStruct struct {
	Name string
	Age  int
}

func inspectType(i interface{}) {
	t := reflect.TypeOf(i)
	v := reflect.ValueOf(i)

	fmt.Printf("\nInspecting value: %v\n", i)
	fmt.Printf("  Type: %v\n", t.Name())
	fmt.Printf("  Kind: %v\n", t.Kind())

	if t.Kind() == reflect.Struct {
		fmt.Printf("  Number of fields: %d\n", t.NumField())
		for i := 0; i < t.NumField(); i++ {
			field := t.Field(i)
			fmt.Printf("    Field %d: %s (Type: %v, Tag: %v)\n", i, field.Name, field.Type, field.Tag)
		}
	}

	if t.Kind() == reflect.Ptr {
		fmt.Printf("  Points to Kind: %v\n", t.Elem().Kind())
		fmt.Printf("  Points to Type: %v\n", t.Elem().Name())
		// To get the value pointed to by a pointer, use Elem() on the Value as well
		if !v.IsNil() {
			fmt.Printf("  Value pointed to: %v\n", v.Elem())
		}
	}
}

func main() {
	inspectType(123)
	inspectType("hello Go")
	inspectType(3.14)
	inspectType(true)

	myS := MyStruct{Name: "Alice", Age: 30}
	inspectType(myS)
	inspectType(&myS)

	var myNilPtr *MyStruct
	inspectType(myNilPtr)

	slice := []int{1, 2, 3}
	inspectType(slice)
}

Using reflect.TypeOf and reflect.ValueOf for detailed type inspection.

Comparing Types

Once you have a reflect.Type object, you can compare it with other reflect.Type objects or with predefined types. This is useful for ensuring that a dynamic type matches an expected type.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var myInt int
	var myString string
	var myFloat float64

	// Get reflect.Type for built-in types
	intType := reflect.TypeOf(myInt)
	stringType := reflect.TypeOf(myString)
	floatType := reflect.TypeOf(myFloat)

	// Example values
	var i interface{} = 42
	var s interface{} = "GoLang"

	// Get reflect.Type of interface values
	valIntType := reflect.TypeOf(i)
	valStringType := reflect.TypeOf(s)

	fmt.Printf("Is 42 an int? %t\n", valIntType == intType)
	fmt.Printf("Is \"GoLang\" a string? %t\n", valStringType == stringType)
	fmt.Printf("Is 42 a float64? %t\n", valIntType == floatType)

	// Comparing Kinds
	fmt.Printf("Kind of 42 is int? %t\n", valIntType.Kind() == reflect.Int)
	fmt.Printf("Kind of \"GoLang\" is string? %t\n", valStringType.Kind() == reflect.String)
}

Comparing reflect.Type objects and reflect.Kind values.