Go Reflection Cheat Sheet
Using reflect.Type and reflect.Value to inspect and mutate values at runtime, plus struct tag parsing for encoders and ORMs.
Type and Value
reflect.TypeOf and reflect.ValueOf are the two entry points into the reflection API.
import "reflect"x := 42t := reflect.TypeOf(x) // reflect.Type: intv := reflect.ValueOf(x) // reflect.Value wrapping 42fmt.Println(t.Kind()) // reflect.Intfmt.Println(t.Name()) // "int"fmt.Println(v.Int()) // 42 (typed accessor, panics if Kind != Int)// Kind is the underlying category (Struct, Slice, Ptr, Int, ...)// Type can differ from Kind for named types, e.g. type MyInt int has Kind() == Int
Inspecting Struct Fields
Iterate fields, read tags, and get/set values via reflection.
type User struct { Name string `json:"name" validate:"required"` Age int `json:"age"`}u := User{Name: "Alice", Age: 30}t := reflect.TypeOf(u)v := reflect.ValueOf(u)for i := 0; i < t.NumField(); i++ { field := t.Field(i) value := v.Field(i) tag := field.Tag.Get("json") fmt.Printf("%s (%s) = %v, json tag=%q\n", field.Name, field.Type, value, tag)}
Mutating Values (Requires a Pointer)
You can only Set through an addressable, settable Value — pass a pointer and call Elem().
func setName(u *User, name string) { v := reflect.ValueOf(u).Elem() // dereference the pointer to get settable Value field := v.FieldByName("Name") if field.IsValid() && field.CanSet() { field.SetString(name) }}u := &User{Name: "Bob"}setName(u, "Robert")fmt.Println(u.Name) // "Robert"// reflect.ValueOf(someNonPointer).Elem() panics -- Elem() needs a Ptr or Interface Kind
Calling Functions Dynamically
reflect.Value.Call invokes a function found via reflection, e.g. for plugin systems.
func add(a, b int) int { return a + b }fn := reflect.ValueOf(add)args := []reflect.Value{reflect.ValueOf(3), reflect.ValueOf(4)}result := fn.Call(args) // []reflect.Valuefmt.Println(result[0].Int()) // 7// Checking a value implements an interfacevar w io.Writert := reflect.TypeOf(os.Stdout)fmt.Println(t.Implements(reflect.TypeOf(&w).Elem()))
Key reflect Concepts
Terminology that trips people up when starting with reflection.
- reflect.Type- static type description: name, kind, methods, fields
- reflect.Value- a boxed runtime value you can inspect/set/call
- Kind()- the underlying category (Struct, Slice, Map, Ptr, Int, Func...)
- Elem()- dereferences a Ptr/Interface Value or gets element type of Slice/Array/Map
- CanSet()- true only for values obtained via an addressable pointer's Elem()
- StructTag- raw tag string on a field, parsed with .Get("key")
Constructing Values with reflect.New
reflect.New(t) allocates a zero value of type t and returns a settable *T-shaped Value, the reflective equivalent of new(T).
t := reflect.TypeOf(User{})ptrVal := reflect.New(t) // reflect.Value wrapping *User, addressableelem := ptrVal.Elem() // dereference to the settable User valueelem.FieldByName("Name").SetString("Grace")elem.FieldByName("Age").SetInt(29)user := ptrVal.Interface().(*User) // recover a real *User to use normallyfmt.Println(*user) // {Grace 29}// reflect.Zero(t) gives a non-addressable zero Value without allocation --// useful for comparisons, not for building up mutable structs.
Calling Methods Dynamically
Value.MethodByName and Type.NumMethod enumerate and invoke a value's method set at runtime, e.g. for plugin/RPC dispatch.
type Greeter struct{ Name string }func (g Greeter) Greet(prefix string) string { return prefix + ", " + g.Name}g := Greeter{Name: "Ada"}v := reflect.ValueOf(g)t := reflect.TypeOf(g)for i := 0; i < t.NumMethod(); i++ { fmt.Println(t.Method(i).Name) // "Greet"}method := v.MethodByName("Greet")if method.IsValid() { out := method.Call([]reflect.Value{reflect.ValueOf("Hello")}) fmt.Println(out[0].String()) // "Hello, Ada"}
Recursive Kind-Switch Encoder
The Kind() switch pattern used inside encoding/json-style libraries to walk arbitrary values.
func encode(v reflect.Value) string { switch v.Kind() { case reflect.Ptr, reflect.Interface: if v.IsNil() { return "null" } return encode(v.Elem()) case reflect.Struct: parts := make([]string, 0, v.NumField()) t := v.Type() for i := 0; i < v.NumField(); i++ { if !t.Field(i).IsExported() { // reflect can see unexported fields but can't Set/Interface() them continue } parts = append(parts, fmt.Sprintf("%q:%s", t.Field(i).Name, encode(v.Field(i)))) } return "{" + strings.Join(parts, ",") + "}" case reflect.Slice, reflect.Array: parts := make([]string, v.Len()) for i := range parts { parts[i] = encode(v.Index(i)) } return "[" + strings.Join(parts, ",") + "]" case reflect.String: return fmt.Sprintf("%q", v.String()) default: return fmt.Sprintf("%v", v.Interface()) }}
reflect.StructOf & DeepEqual
StructOf builds an entirely new struct type at runtime (used by some ORMs/GraphQL libraries); DeepEqual is the recursive comparison reflection powers under the hood.
dynType := reflect.StructOf([]reflect.StructField{ {Name: "Name", Type: reflect.TypeOf(""), Tag: `json:"name"`}, {Name: "Score", Type: reflect.TypeOf(0.0), Tag: `json:"score"`},})instance := reflect.New(dynType).Elem()instance.FieldByName("Name").SetString("dynamic")fmt.Println(instance.Interface()) // {dynamic 0}// reflect.DeepEqual recursively compares Kind-appropriate fields --// this is what testify/require.Equal and cmp.Diff build on top of.a := map[string]int{"x": 1}b := map[string]int{"x": 1}fmt.Println(reflect.DeepEqual(a, b)) // true, even though maps aren't == comparable
Reflection Gotchas
Runtime panics and subtle mistakes unique to reflect-heavy code.
- Unexported field access- Field()/FieldByName() can read unexported fields, but .Interface() and .Set() on them panic with 'reflect: reflect.Value.Interface: cannot return value obtained from unexported field'
- CanAddr() vs CanSet()- a Value can be addressable but still not settable if it came from an unexported field or a non-pointer receiver
- reflect.ValueOf(x).Elem() on non-pointer- panics with 'call of reflect.Value.Elem on <kind> Value' unless x is a Ptr or Interface
- Comparing reflect.Type with ==- safe and fast (types are interned/comparable), unlike comparing arbitrary reflect.Value
- Nil interface vs nil pointer wrapped in interface- v.IsNil() is required to detect the latter; a plain `== nil` check on the interface is famously wrong
- Cost of repeated TypeOf/ValueOf- each call walks runtime type metadata; cache reflect.Type lookups keyed by go type in hot encoders
Reach for reflection only at the edges of your program (encoders, ORMs, dependency injection, generic-ish utilities before generics existed) — it bypasses compile-time type checking and is noticeably slower than direct code, so keep it out of hot paths and prefer Go generics where they now suffice.