Reflection in Go: Inspecting and Changing Values at Runtime
reflect.TypeOf and ValueOf, Type vs Kind, setting values through pointers, reading struct tags, and when not to use reflection.
On this page
Reflection lets a program inspect and modify values whose types are only known at runtime. You rarely write reflection yourself, but you use it all the time: encoding/json, fmt, validators and ORMs are built on the reflect package. Understanding it explains how struct tags work and why some operations panic.
Type and Value
Two functions are the entry points:
reflect.TypeOf(x) returns a reflect.Type: what the value is.reflect.ValueOf(x) returns a reflect.Value: the value itself, with methods to read (and sometimes write) it.x := 10
v := reflect.ValueOf(x)
t := v.Type()
fmt.Println(v) // 10
fmt.Println(v.IsZero()) // false
fmt.Println(t) // int
fmt.Println(t.Kind()) // int
fmt.Println(t.Kind() == reflect.Int) // trueType vs Kind
The Type is the exact type, including your custom names. The Kind is the underlying category: Int, String, Struct, Slice, Map, Ptr and so on.
type UserID int
var id UserID = 7
t := reflect.TypeOf(id)
fmt.Println(t) // main.UserID
fmt.Println(t.Kind()) // intGeneric reflection code usually switches on Kind, because it cares about the shape, not the name.
Changing a value at runtime
To modify a variable through reflection, you must reflect on a pointer and then call Elem() to reach the value it points to:
id := 101
p := reflect.ValueOf(&id) // a *int
e := p.Elem() // the int it points to
fmt.Println(p.Type()) // *int
fmt.Println(e.Int()) // 101
fmt.Println(e.CanSet()) // true
e.SetInt(100)
fmt.Println(id) // 100, the original variable changedReflecting on the value directly gives you a copy that cannot be set:
reflect.ValueOf(id).SetInt(5)
// panic: reflect: reflect.Value.SetInt using unaddressable valueIt is the same rule as function arguments: to change something, you need its address.
Reading struct fields and tags
This is how encoding/json knows to use "name" instead of Name. Struct tags are metadata strings that reflection can read:
type User struct {
Name string `json:"name" validate:"required"`
Email string `json:"email"`
age int
}
t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("%s json=%q validate=%q exported=%v\n",
f.Name, f.Tag.Get("json"), f.Tag.Get("validate"), f.IsExported())
}Name json="name" validate="required" exported=true
Email json="email" validate="" exported=true
age json="" validate="" exported=falseA validator library reads the validate tag exactly like this, and JSON ignores age because unexported fields cannot be accessed from another package.
Working with unknown values
With any, reflection can discover what you received:
var unknown any = "This is an unknown type"
v := reflect.ValueOf(unknown)
if v.Kind() == reflect.String {
fmt.Println("string:", v.String())
}For a fixed set of possibilities, a type switch is simpler, faster and type-safe; reach for reflection only when the types are truly open-ended.
Costs and cautions
Common panics to watch for:
SetInt on an unaddressable value (forgot the pointer and Elem()).Int() on a value whose kind is not an integer.CanSet() returns false).reflect.Value (for example, ValueOf(nil)).Key takeaways
TypeOf tells you the type; ValueOf gives you the value.reflect.ValueOf(&x).Elem().Field(i).Tag.Get("key"); this powers JSON and validation.Last updated on