Reflection in Practice
Overview
The reflect package provides runtime type inspection. Use sparingly—prefer generics or interfaces when possible.
Type and Value
import "reflect"
x := 42
t := reflect.TypeOf(x) // Type information
v := reflect.ValueOf(x) // Value information
fmt.Println(t.Name()) // int
fmt.Println(t.Kind()) // int
fmt.Println(v.Int()) // 42Inspecting Structs
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
u := User{Name: "Alice", Email: "a@b.com"}
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: %v (tag: %s)\n", field.Name, value, tag)
}Modifying Values
x := 42
v := reflect.ValueOf(&x).Elem() // Need pointer for modification
v.SetInt(100)
fmt.Println(x) // 100Calling Methods
type Calculator struct{}
func (c Calculator) Add(a, b int) int { return a + b }
c := Calculator{}
v := reflect.ValueOf(c)
method := v.MethodByName("Add")
args := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)}
result := method.Call(args)
fmt.Println(result[0].Int()) // 5When to Use
✅ Appropriate: - Serialization/deserialization - ORM mapping - Dependency injection - Test utilities
❌ Avoid: - Performance-critical paths - Simple type conversions - When generics suffice
Summary
| Function | Purpose |
|---|---|
TypeOf() |
Get type info |
ValueOf() |
Get value wrapper |
Kind() |
Base type category |
Field() |
Access struct field |
MethodByName() |
Access method |
Worked example
Copy exported fields from map[string]any into a struct via reflection.
Save as main.go. Then:
go mod init example
go run .package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
}
func mapToStruct(m map[string]any, dest any) error {
v := reflect.ValueOf(dest)
if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct {
return fmt.Errorf("dest must be *struct")
}
v = v.Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if !f.IsExported() {
continue
}
raw, ok := m[f.Name]
if !ok {
continue
}
fv := v.Field(i)
rv := reflect.ValueOf(raw)
if !rv.Type().AssignableTo(fv.Type()) {
return fmt.Errorf("field %s: cannot assign %T", f.Name, raw)
}
fv.Set(rv)
}
return nil
}
func main() {
var u User
err := mapToStruct(map[string]any{"Name": "Ada", "Age": 36}, &u)
fmt.Println("user:", u, "err:", err)
}Expected output:
user: {Ada 36} err: <nil>
More examples
Read struct tags (serializer-style).
package main
import (
"fmt"
"reflect"
)
type Row struct {
ID int `json:"id"`
Email string `json:"email,omitempty"`
Skip string `json:"-"`
}
func main() {
t := reflect.TypeOf(Row{})
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("%s tag=%q\n", f.Name, f.Tag.Get("json"))
}
}Expected output:
ID tag="id"
Email tag="email,omitempty"
Skip tag="-"
Runnable example
Save as main.go. Then:
go mod init example
go run .package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
age int // unexported: visible to reflect only within this package
}
type Calculator struct{}
func (Calculator) Add(a, b int) int { return a + b }
func main() {
u := User{Name: "Alice", Email: "a@b.com", age: 30}
t := reflect.TypeOf(u)
v := reflect.ValueOf(u)
fmt.Println("type:", t.Name(), "kind:", t.Kind())
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("field %s tag=%q value=%v\n", f.Name, f.Tag.Get("json"), v.Field(i).Interface())
}
// Modify via pointer + Elem
x := 42
rv := reflect.ValueOf(&x).Elem()
rv.SetInt(100)
fmt.Println("modified x:", x)
// Call method by name
c := Calculator{}
m := reflect.ValueOf(c).MethodByName("Add")
out := m.Call([]reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)})
fmt.Println("Add via reflect:", out[0].Int())
}Expected output:
type: User kind: struct
field Name tag="name" value=Alice
field Email tag="email" value=a@b.com
field age tag="" value=30
modified x: 100
Add via reflect: 5
What to notice: ValueOf(x) is not settable—you need a pointer and Elem(). Tags power serializers (JSON, ORMs). Prefer generics/interfaces when you know the types at compile time; reflection costs CPU and clarity.
Try next: Build a tiny MapToStruct helper that fills exported fields from map[string]any. Compare it to a typed constructor.