Why This Matters - The Runtime Superpower
Imagine building a system that needs to handle data types you can't predict at compile time. Before reflection, this was impossible. Go's reflection package gives you the ability to examine and manipulate your code while it's runningβunlocking possibilities that transform the Go ecosystem.
The Real-World Impact:
- JSON/ORM Libraries:
encoding/jsonand GORM handle millions of different types with one implementation - Testing Frameworks: Deep equality comparison works for any struct without custom code
- Configuration Systems: Environment variables map automatically to struct fields
- API Libraries: Universal clients adapt to any response structure
π‘ Key Takeaway: Reflection gives you X-ray vision for your code, enabling libraries that work with ANY type while maintaining flexibility and power.
Learning Objectives
By the end of this article, you will be able to:
- Use
reflect.Typeandreflect.Valueto inspect and manipulate any object - Build generic utilities that work with unknown types at runtime
- Understand the performance trade-offs and when reflection is worth it
- Create production-ready reflection code with proper error handling
- Apply reflection patterns used in major Go libraries like
encoding/jsonand GORM
Real-World Impact:
JSON Marshaling - The foundation of web APIs:
1// Without reflection: Need custom marshal for EVERY type
2type User struct{ Name string; Age int }
3func MarshalJSON() {
4 return []byte(fmt.Sprintf(`{"name":"%s","age":%d}`, u.Name, u.Age)), nil
5}
6// 1000+ types = 1000+ manual implementations!
7
8// With reflection: One encoder handles ALL types
9json.Marshal(user) // Works for any struct, automatically
encoding/json uses reflection to scan struct fields and tags at runtimeβhandles millions of types with zero code duplication.
ORM Libraries - Database mapping without boilerplate:
- GORM handles 100+ database types using reflection
- Scans struct tags to generate SQL:
gorm:"primaryKey;autoIncrement" - Without reflection: Manual SQL for every struct
Testing Frameworks - Deep equality and mocking:
1// reflect.DeepEqual compares any types, including private fields
2assert.Equal(t, expected, actual) // Works for structs, maps, slices, etc.
3
4// testify/mock uses reflection to verify method calls
5mockDB.On("Query", "SELECT * FROM users").Return(users, nil)
Go Reflection vs Other Languages
| Capability | Go | Java | Python | C# | JavaScript |
|---|---|---|---|---|---|
| Type inspection | β reflect.TypeOf() | β Class.forName() | β type() | β Type.GetType() | β οΈ typeof |
| Modify values | β Value.Set() | β Field.set() | β setattr() | β FieldInfo.SetValue() | β obj[key] = val |
| Call methods | β Value.Call() | β Method.invoke() | β getattr() | β MethodInfo.Invoke() | β objmethod |
| Create instances | β reflect.New() | β Constructor.newInstance() | β type() | β Activator.CreateInstance() | β new Class() |
| Performance | β οΈ 10-100x slower | β οΈ 10-50x slower | β Built-in | β οΈ 10-50x slower | β Native |
| Type safety | β Compile-time + runtime | β οΈ Runtime only | β Dynamic typing | β οΈ Runtime only | β Dynamic typing |
| Generics integration | β Since Go 1.18 | β Type erasure | N/A | β Reification | N/A |
Why Go's Reflection is Unique:
- Strict type system - Reflection respects Go's type safety
- Unexported fields - Private fields accessible for reading but not writing
- Interface{} foundation -
reflect.ValueOf(any)works on any value - Pointer semantics - Explicit
.Elem()and.Addr()for pointer handling - No runtime tricks - No hidden vtables or dynamic dispatch
Performance Reality Check
1// Benchmark: Direct access vs Reflection
2Direct field access: 2ms
3Type assertion: 3ms
4Reflection read: 25ms
5Reflection write: 45ms
6reflect.DeepEqual: 150ms
When Reflection is Worth It:
- Setup/initialization code - Runs once, performance doesn't matter
- Testing - Developer time > CPU time
- Libraries - One reflection implementation serves thousands of users
- Configuration - Loading config at startup
When to Avoid Reflection:
- Hot paths - Called millions of times per second
- Simple type switches -
switch v.(type)is 50x faster - Generics solve it - Use
func Process[T any](v T)instead
Real-World Use Cases from Production
1. encoding/json - Web API Backbone
1type APIResponse struct {
2 Status string `json:"status"`
3 Data any `json:"data"`
4 Error string `json:"error,omitempty"`
5}
6
7// Reflection marshals ANY type without custom code
8json.Marshal(APIResponse{Status: "ok", Data: user}) // Works!
9json.Marshal(APIResponse{Status: "ok", Data: []Product{}}) // Also works!
Impact: Handles millions of types across entire Go ecosystem with single implementation.
2. GORM - Database ORM
1type User struct {
2 ID uint `gorm:"primaryKey"`
3 Name string `gorm:"index"`
4 CreatedAt time.Time
5}
6
7// Reflection scans struct tags to generate SQL
8db.AutoMigrate(&User{})
9// Generates: CREATE TABLE users, created_at DATETIME)
Impact: Eliminates 90% of boilerplate SQL generation code.
3. github.com/stretchr/testify - Testing Framework
1// reflect.DeepEqual handles nested structs, unexported fields, etc.
2assert.Equal(t, expectedUser, actualUser) // Compares ALL fields, even private ones
Impact: Makes testing 10x easier without manual field-by-field comparison.
When to Use Reflection: The Doctor's Dilemma
Think of reflection like a doctor's toolbox. You wouldn't use surgery for a common cold, but you absolutely need it for complex internal diagnoses.
Real-world Example:
Consider a universal remote control that can work with any TV brand. Some TVs use infrared, some use Bluetooth, some use Wi-Fi. Your remote needs to discover what type of TV it's talking to and adapt accordinglyβthis is exactly what reflection does for your code!
1// Without reflection: One remote per TV brand
2func ControlSonyTV(tv SonyTV) { /* Sony-specific code */ }
3func ControlLGTV(tv LGTV) { /* LG-specific code */ }
4func ControlSamsungTV(tv SamsungTV) { /* Samsung-specific code */ }
5
6// With reflection: One remote for all TVs
7func ControlAnyTV(tv interface{}) {
8 v := reflect.ValueOf(tv)
9 // Discover TV capabilities and control accordingly
10}
β Use Reflection For:
- Serialization libraries - JSON, XML, Protocol Buffers
- ORM and database mapping - GORM, SQLBoiler
- Configuration parsing - Viper, envconfig
- Testing frameworks - testify, gomock
- Dependency injection - Wire, dig
- Generic utilities - Deep copy, deep equal
β Avoid Reflection For:
- Hot path operations - Called millions of times
- Simple type switches - Use
switch v.(type)instead - Known types at compile time - Use generics or interfaces
- Performance-critical code - 10-100x slower than direct access
β οΈ Important: Reflection is powerful but comes with a performance cost. Use it when flexibility outweighs raw speed, or when it's the only tool that can solve the problem.
Decision Tree:
Do you know all types at compile time?
ββ Yes β Can generics solve it)?
β ββ Yes β β
USE GENERICS
β ββ No β Can interfaces solve it?
β ββ Yes β β
USE INTERFACES
β ββ No β Is it in a hot path?
β ββ Yes β β AVOID REFLECTION
β ββ No β β
USE REFLECTION
ββ No β Need to handle unknown types at runtime?
ββ Yes β β
USE REFLECTION
ββ No β Reconsider design
When to Use Reflection
Essential use cases where reflection is the only solution:
- Serialization/deserialization - JSON, XML, protobuf
- ORM and database mapping - Map database rows to structs dynamically
- Generic library functions - Work with any type
- Testing and mocking frameworks - Inspect private fields, verify method calls
- Dependency injection - Wire dependencies based on types
- Configuration loading - Map environment variables to struct fields
Avoid reflection when:
- Static typing works fine - Use generics:
func Max[T Ordered](a, b T) - Performance is critical - Reflection is 10-100x slower than direct access
- Code clarity is more important - Reflection makes code harder to understand
Type and Value
Basic Type Inspection: Your First Look Inside
Let's start with the foundation of reflectionβexamining types and values at runtime. Think of this as putting on your X-ray glasses for the first time.
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func main() {
9 var x float64 = 3.14
10
11 // Examine the "blueprint"
12 t := reflect.TypeOf(x)
13 fmt.Println("Type:", t) // float64 - the exact type
14 fmt.Println("Kind:", t.Kind()) // float64 - the category
15 fmt.Println("Name:", t.Name()) // float64 - the type's name
16
17 // Examine the "actual object"
18 v := reflect.ValueOf(x)
19 fmt.Println("Value:", v) // 3.14 - the value
20 fmt.Println("Type:", v.Type()) // float64 - type info
21 fmt.Println("Kind:", v.Kind()) // float64 - category
22 fmt.Println("Float:", v.Float()) // 3.14 - value as float64
23 fmt.Println("Interface:", v.Interface()) // 3.14 - back to normal
24}
What's Happening Under the Hood:
Think of reflection as giving you two special powers:
- π Type Inspection - Like looking at the blueprint of a house
- π¦ Value Inspection - Like examining the actual house
Real-world Analogy:
Think of yourself as a detective investigating a mysterious package:
TypeOf(x)tells you "this is a box labeled 'fragile'"ValueOf(x)lets you actually open the box and see what's inside
π‘ Key Concepts:
- Type vs Kind:
Typeis the specific type,Kindis the category - Type Information: The "blueprint" or metadata about the type
- Value Information: The actual data and its properties
β οΈ Important Trade-off:
Reflection is incredibly powerful but comes with a performance cost. It's like using a high-powered microscopeβit gives you amazing detail but takes longer than just looking with your eyes.
When to use each:
- Use
TypeOf()when you need to understand the structure - Use
ValueOf()when you need to access or modify the actual data
Type vs Kind
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8type MyInt int
9
10func main() {
11 var x MyInt = 42
12
13 t := reflect.TypeOf(x)
14 fmt.Println("Type:", t) // main.MyInt
15 fmt.Println("Kind:", t.Kind()) // int
16 fmt.Println("Name:", t.Name()) // MyInt
17
18 // Kind is the underlying type
19 // Type is the specific named type
20}
Struct Inspection
Reading Struct Fields: Exploring the Building Blocks
Structs are the building blocks of Go programs, and reflection lets you explore every nook and cranny. Think of this like being able to read the architectural blueprint of any building, even ones you've never seen before.
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8type Person struct {
9 Name string
10 Age int
11 Email string `json:"email" validate:"required"`
12}
13
14func main() {
15 p := Person{Name: "Alice", Age: 30, Email: "alice@example.com"}
16
17 // Get the "architectural blueprint" of the struct
18 t := reflect.TypeOf(p)
19 fmt.Println("Type:", t)
20 fmt.Println("NumField:", t.NumField())
21
22 // Walk through each "room" in the building
23 for i := 0; i < t.NumField(); i++ {
24 field := t.Field(i)
25 fmt.Printf("Field %d: %s\n", i, field.Name, field.Type)
26 fmt.Printf(" Tag: %s\n", field.Tag)
27 fmt.Printf(" JSON tag: %s\n", field.Tag.Get("json"))
28 }
29}
Real-world Example: Configuration Loading
Consider building a universal configuration loader that can work with any config struct:
1// Any config struct can be automatically populated from environment variables
2type DatabaseConfig struct {
3 Host string `env:"DB_HOST" default:"localhost"`
4 Port int `env:"DB_PORT" default:"5432"`
5 Username string `env:"DB_USER" required:"true"`
6 Password string `env:"DB_PASS" required:"true"`
7}
8
9type ServerConfig struct {
10 Port int `env:"SERVER_PORT" default:"8080"`
11 Environment string `env:"ENV" default:"development"`
12 Debug bool `env:"DEBUG" default:"false"`
13}
14
15// One function handles ANY config struct!
16func LoadConfig(config interface{}) error {
17 v := reflect.ValueOf(config).Elem()
18 t := v.Type()
19
20 for i := 0; i < t.NumField(); i++ {
21 field := t.Field(i)
22 envTag := field.Tag.Get("env")
23
24 // Read from environment and set field value
25 if envValue := os.Getenv(envTag); envValue != "" {
26 v.Field(i).SetString(envValue)
27 }
28 }
29 return nil
30}
π‘ Key Takeaway: Struct inspection with reflection enables incredibly flexible systems like configuration loaders, validation frameworks, and serialization libraries that can work with any struct without knowing its structure beforehand.
Common Pitfalls:
β Forgetting to check field visibility: Unexported fields can't be accessed
β
Always check field.CanInterface() before accessing
β Ignoring struct tags: Tags contain important metadata
β
Use field.Tag.Get() to extract configuration information
Reading Field Values
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8type Person struct {
9 Name string
10 Age int
11 Email string
12}
13
14func main() {
15 p := Person{Name: "Alice", Age: 30, Email: "alice@example.com"}
16
17 v := reflect.ValueOf(p)
18
19 for i := 0; i < v.NumField(); i++ {
20 field := v.Field(i)
21 fieldType := v.Type().Field(i)
22
23 fmt.Printf("%s = %v\n", fieldType.Name, field.Interface())
24 }
25}
Struct Tags
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8type User struct {
9 ID int `json:"id" db:"user_id" validate:"required"`
10 Username string `json:"username" db:"username" validate:"min=3,max=20"`
11 Email string `json:"email" db:"email" validate:"email"`
12}
13
14func printTags(s interface{}) {
15 t := reflect.TypeOf(s)
16
17 for i := 0; i < t.NumField(); i++ {
18 field := t.Field(i)
19 fmt.Printf("\nField: %s\n", field.Name)
20 fmt.Printf(" json: %s\n", field.Tag.Get("json"))
21 fmt.Printf(" db: %s\n", field.Tag.Get("db"))
22 fmt.Printf(" validate: %s\n", field.Tag.Get("validate"))
23 }
24}
25
26func main() {
27 user := User{}
28 printTags(user)
29}
Modifying Values
Setting Values
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func main() {
9 var x float64 = 3.14
10
11 v := reflect.ValueOf(&x) // Must pass pointer to modify
12 v = v.Elem() // Get the value pointed to
13
14 fmt.Println("Settable:", v.CanSet()) // true
15
16 v.SetFloat(7.28)
17 fmt.Println("x:", x) // 7.28
18}
What's Happening:
reflect.ValueOf(&x)creates a Value representing the pointer to x.Elem()dereferences the pointer to get the actual variable.CanSet()checks if the value is addressable and exported.SetFloat()modifies the original variable through reflection
Why This Works:
- Addressability rule: You can only modify a value if you have a pointer to it
- Passing
xdirectly creates a copyβmodifying the copy wouldn't affect the original .Elem()is like dereferencing a pointer
Common Mistake:
1// β Wrong: Passing value instead of pointer
2v := reflect.ValueOf(x) // Copy of x
3v.SetFloat(7.28) // PANIC: value not settable
4
5// β
Correct: Pass pointer, then dereference
6v := reflect.ValueOf(&x).Elem()
7v.SetFloat(7.28) // Works!
Modifying Struct Fields
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8type Person struct {
9 Name string
10 Age int
11}
12
13func main() {
14 p := Person{Name: "Alice", Age: 30}
15 fmt.Println("Before:", p)
16
17 v := reflect.ValueOf(&p).Elem()
18
19 nameField := v.FieldByName("Name")
20 if nameField.CanSet() {
21 nameField.SetString("Bob")
22 }
23
24 ageField := v.FieldByName("Age")
25 if ageField.CanSet() {
26 ageField.SetInt(25)
27 }
28
29 fmt.Println("After:", p)
30}
Unexported Fields
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8type Person struct {
9 Name string
10 age int // unexported
11}
12
13func main() {
14 p := Person{Name: "Alice", age: 30}
15
16 v := reflect.ValueOf(&p).Elem()
17
18 // Name can be set
19 nameField := v.FieldByName("Name")
20 fmt.Println("Name CanSet:", nameField.CanSet()) // true
21
22 // age cannot be set
23 ageField := v.FieldByName("age")
24 fmt.Println("age CanSet:", ageField.CanSet()) // false
25
26 // Can read unexported fields
27 fmt.Println("age value:", ageField.Int()) // 30
28}
Function Inspection
Inspecting Function Signature
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func Add(a, b int) int {
9 return a + b
10}
11
12func Process(name string, age int, active bool) {
13 return name, nil
14}
15
16func inspectFunc(fn interface{}) {
17 t := reflect.TypeOf(fn)
18
19 fmt.Println("Function Type:", t)
20 fmt.Println("NumIn:", t.NumIn())
21 fmt.Println("NumOut:", t.NumOut())
22
23 fmt.Println("\nInput parameters:")
24 for i := 0; i < t.NumIn(); i++ {
25 fmt.Printf(" %d: %s\n", i, t.In(i))
26 }
27
28 fmt.Println("\nOutput parameters:")
29 for i := 0; i < t.NumOut(); i++ {
30 fmt.Printf(" %d: %s\n", i, t.Out(i))
31 }
32}
33
34func main() {
35 fmt.Println("=== Add ===")
36 inspectFunc(Add)
37
38 fmt.Println("\n=== Process ===")
39 inspectFunc(Process)
40}
Calling Functions
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func Add(a, b int) int {
9 return a + b
10}
11
12func Greet(name string) string {
13 return "Hello, " + name
14}
15
16func main() {
17 // Call Add
18 addFunc := reflect.ValueOf(Add)
19 args := []reflect.Value{
20 reflect.ValueOf(5),
21 reflect.ValueOf(10),
22 }
23 results := addFunc.Call(args)
24 fmt.Println("Add result:", results[0].Int()) // 15
25
26 // Call Greet
27 greetFunc := reflect.ValueOf(Greet)
28 args = []reflect.Value{
29 reflect.ValueOf("Alice"),
30 }
31 results = greetFunc.Call(args)
32 fmt.Println("Greet result:", results[0].String()) // Hello, Alice
33}
Creating Values
Creating New Instances
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8type Person struct {
9 Name string
10 Age int
11}
12
13func main() {
14 // Create new instance
15 t := reflect.TypeOf(Person{})
16 v := reflect.New(t) // Returns pointer
17 v.Elem().FieldByName("Name").SetString("Alice")
18 v.Elem().FieldByName("Age").SetInt(30)
19
20 person := v.Interface().(*Person)
21 fmt.Printf("%+v\n", person)
22
23 // Create slice
24 sliceType := reflect.TypeOf([]int{})
25 slice := reflect.MakeSlice(sliceType, 5, 10)
26 fmt.Println("Slice:", slice)
27
28 // Create map
29 mapType := reflect.TypeOf(map[string]int{})
30 m := reflect.MakeMap(mapType)
31 m.SetMapIndex(reflect.ValueOf("key"), reflect.ValueOf(42))
32 fmt.Println("Map:", m)
33}
Creating Slices and Maps
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func main() {
9 // Create slice dynamically
10 sliceType := reflect.SliceOf(reflect.TypeOf(0))
11 slice := reflect.MakeSlice(sliceType, 0, 10)
12
13 slice = reflect.Append(slice, reflect.ValueOf(1))
14 slice = reflect.Append(slice, reflect.ValueOf(2))
15 slice = reflect.Append(slice, reflect.ValueOf(3))
16
17 fmt.Println("Slice:", slice.Interface()) // [1 2 3]
18
19 // Create map dynamically
20 mapType := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
21 m := reflect.MakeMap(mapType)
22
23 m.SetMapIndex(reflect.ValueOf("one"), reflect.ValueOf(1))
24 m.SetMapIndex(reflect.ValueOf("two"), reflect.ValueOf(2))
25
26 fmt.Println("Map:", m.Interface()) // map[one:1 two:2]
27}
Practical Examples
Generic Print Function
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func Print(v interface{}) {
9 val := reflect.ValueOf(v)
10 typ := reflect.TypeOf(v)
11
12 fmt.Printf("Type: %s, Kind: %s\n", typ, val.Kind())
13
14 switch val.Kind() {
15 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
16 fmt.Printf("Integer: %d\n", val.Int())
17
18 case reflect.Float32, reflect.Float64:
19 fmt.Printf("Float: %f\n", val.Float())
20
21 case reflect.String:
22 fmt.Printf("String: %s\n", val.String())
23
24 case reflect.Struct:
25 fmt.Println("Struct fields:")
26 for i := 0; i < val.NumField(); i++ {
27 field := val.Field(i)
28 fieldType := typ.Field(i)
29 fmt.Printf(" %s: %v\n", fieldType.Name, field.Interface())
30 }
31
32 case reflect.Slice, reflect.Array:
33 fmt.Printf("Length: %d\n", val.Len())
34 for i := 0; i < val.Len(); i++ {
35 fmt.Printf(" [%d]: %v\n", i, val.Index(i).Interface())
36 }
37
38 case reflect.Map:
39 fmt.Println("Map entries:")
40 for _, key := range val.MapKeys() {
41 fmt.Printf(" %v: %v\n", key, val.MapIndex(key))
42 }
43
44 default:
45 fmt.Printf("Value: %v\n", v)
46 }
47}
48
49func main() {
50 Print(42)
51 Print(3.14)
52 Print("hello")
53 Print([]int{1, 2, 3})
54 Print(map[string]int{"a": 1, "b": 2})
55
56 type Person struct {
57 Name string
58 Age int
59 }
60 Print(Person{Name: "Alice", Age: 30})
61}
Deep Equal
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func main() {
9 a := []int{1, 2, 3}
10 b := []int{1, 2, 3}
11 c := []int{1, 2, 4}
12
13 fmt.Println("a == b:", reflect.DeepEqual(a, b)) // true
14 fmt.Println("a == c:", reflect.DeepEqual(a, c)) // false
15
16 type Person struct {
17 Name string
18 Age int
19 }
20
21 p1 := Person{Name: "Alice", Age: 30}
22 p2 := Person{Name: "Alice", Age: 30}
23 p3 := Person{Name: "Bob", Age: 30}
24
25 fmt.Println("p1 == p2:", reflect.DeepEqual(p1, p2)) // true
26 fmt.Println("p1 == p3:", reflect.DeepEqual(p1, p3)) // false
27}
Generics vs Reflection - When to Use Which?
This is a common point of confusion. Both seem to solve similar problems, but they're optimized for different use cases.
Real-world Analogy:
- Generics are like having a master key that opens any door of a specific type
- Reflection is like being a master locksmith who can pick any lock, even ones you've never seen before
When to Use Generics β
1// You know the types at compile time, but want to reuse code
2func Max[T constraints.Ordered](a, b T) T {
3 if a > b { return a }
4 return b
5}
6
7// Works with int, float64, string, etc.
8// Compile-time checked, zero runtime overhead
9result := Max(5, 10) // Fast! Compiler knows these are ints
Use generics when:
- β You know all possible types at compile time
- β Performance matters
- β You want compile-time type safety
- β Code is in the "hot path"
When to Use Reflection β
1// You need to handle types you've never seen before
2func PrintAllFields(obj interface{}) {
3 v := reflect.ValueOf(obj)
4 for i := 0; i < v.NumField(); i++ {
5 fmt.Printf("%s: %v\n", v.Type().Field(i).Name, v.Field(i).Interface())
6 }
7}
8
9// Works with ANY struct, even ones that don't exist yet!
10// Runtime discovery, 10-100x slower
Use reflection when:
- β You need to handle unknown types at runtime
- β You're building libraries/frameworks
- β You need to inspect struct fields or tags
- β You're doing one-time setup
Decision Guide
Do you know all types at compile time?
ββ Yes β Can generics solve it?
β ββ Yes β β
USE GENERICS
β ββ No β Is performance critical?
β ββ Yes β β RECONSIDER DESIGN
β ββ No β β
USE REFLECTION
ββ No β β
USE REFLECTION
Performance Considerations
Reflection is Slow
1package main
2
3import (
4 "reflect"
5 "testing"
6)
7
8type Point struct {
9 X, Y int
10}
11
12func DirectAccess(p Point) int {
13 return p.X + p.Y
14}
15
16func ReflectionAccess(p Point) int {
17 v := reflect.ValueOf(p)
18 x := v.FieldByName("X").Int()
19 y := v.FieldByName("Y").Int()
20 return int(x + y)
21}
22
23func BenchmarkDirectAccess(b *testing.B) {
24 p := Point{X: 5, Y: 10}
25 for i := 0; i < b.N; i++ {
26 DirectAccess(p)
27 }
28}
29
30func BenchmarkReflectionAccess(b *testing.B) {
31 p := Point{X: 5, Y: 10}
32 for i := 0; i < b.N; i++ {
33 ReflectionAccess(p)
34 }
35}
Cache Reflection Results
1package main
2
3import (
4 "reflect"
5 "sync"
6)
7
8type StructInfo struct {
9 Type reflect.Type
10 Fields []reflect.StructField
11}
12
13var (
14 cache = make(map[reflect.Type]*StructInfo)
15 mu sync.RWMutex
16)
17
18func GetStructInfo(v interface{}) *StructInfo {
19 t := reflect.TypeOf(v)
20
21 mu.RLock()
22 info, exists := cache[t]
23 mu.RUnlock()
24
25 if exists {
26 return info
27 }
28
29 mu.Lock()
30 defer mu.Unlock()
31
32 // Double-check after acquiring write lock
33 if info, exists := cache[t]; exists {
34 return info
35 }
36
37 info = &StructInfo{
38 Type: t,
39 Fields: make([]reflect.StructField, t.NumField()),
40 }
41
42 for i := 0; i < t.NumField(); i++ {
43 info.Fields[i] = t.Field(i)
44 }
45
46 cache[t] = info
47 return info
48}
Best Practices
1. Cache Type and Field Information
β Correct: Cache reflection metadata
1type StructCache struct {
2 fields map[reflect.Type][]reflect.StructField
3 mu sync.RWMutex
4}
5
6var cache = &StructCache{fields: make(map[reflect.Type][]reflect.StructField)}
7
8func GetFields(t reflect.Type) []reflect.StructField {
9 c.mu.RLock()
10 if fields, ok := c.fields[t]; ok {
11 c.mu.RUnlock()
12 return fields
13 }
14 c.mu.RUnlock()
15
16 // Cache miss - compute once
17 c.mu.Lock()
18 defer c.mu.Unlock()
19
20 fields := make([]reflect.StructField, t.NumField())
21 for i := 0; i < t.NumField(); i++ {
22 fields[i] = t.Field(i)
23 }
24 c.fields[t] = fields
25 return fields
26}
β Wrong: Repeated reflection calls
1func ProcessStructs(items []any) {
2 for _, item := range items {
3 t := reflect.TypeOf(item) // Called N times!
4 for i := 0; i < t.NumField(); i++ {
5 field := t.Field(i) // Expensive lookup every time
6 }
7 }
8}
Why: Type/field lookups are expensive. Cache them for 10-100x speedup.
2. Always Check CanSet() Before Modifying
β Correct: Check before setting
1func SetField(obj any, fieldName string, value any) error {
2 v := reflect.ValueOf(obj)
3 if v.Kind() == reflect.Ptr {
4 v = v.Elem()
5 }
6
7 field := v.FieldByName(fieldName)
8 if !field.IsValid() {
9 return fmt.Errorf("field %s not found", fieldName)
10 }
11 if !field.CanSet() {
12 return fmt.Errorf("field %s cannot be set", fieldName)
13 }
14
15 field.Set(reflect.ValueOf(value))
16 return nil
17}
β Wrong: No checks
1func SetField(obj any, fieldName string, value any) {
2 v := reflect.ValueOf(obj).Elem()
3 v.FieldByName(fieldName).Set(reflect.ValueOf(value)) // PANIC if unexported!
4}
Why: Setting unexported fields or non-pointer values causes runtime panics.
3. Use Type Switches Instead of Reflection When Types Are Known
β Correct: Type switch
1func Format(v any) string {
2 switch val := v.(type) {
3 case int:
4 return fmt.Sprintf("%d", val)
5 case string:
6 return val
7 case bool:
8 return fmt.Sprintf("%t", val)
9 default:
10 return fmt.Sprintf("%v", val)
11 }
12}
β Wrong: Reflection for known types
1func Format(v any) string {
2 val := reflect.ValueOf(v)
3 switch val.Kind() {
4 case reflect.Int:
5 return fmt.Sprintf("%d", val.Int())
6 case reflect.String:
7 return val.String()
8 case reflect.Bool:
9 return fmt.Sprintf("%t", val.Bool())
10 }
11 return ""
12}
Why: Type switches are compile-time optimized. Use reflection only for unknown types.
4. Handle Nil Values Properly
β Correct: Check for nil
1func IsNil(v any) bool {
2 if v == nil {
3 return true
4 }
5
6 rv := reflect.ValueOf(v)
7 switch rv.Kind() {
8 case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func:
9 return rv.IsNil()
10 default:
11 return false
12 }
13}
β Wrong: Calling IsNil() on non-nullable types
1func IsNil(v any) bool {
2 return reflect.ValueOf(v).IsNil() // PANIC for int, struct, etc.!
3}
Why: IsNil() panics on types that can't be nil.
5. Use Elem() Correctly for Pointers
β Correct: Check kind before Elem()
1func GetValue(v any) reflect.Value {
2 rv := reflect.ValueOf(v)
3 for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
4 rv = rv.Elem()
5 }
6 return rv
7}
β Wrong: Calling Elem() blindly
1func GetValue(v any) reflect.Value {
2 return reflect.ValueOf(v).Elem() // PANIC if not pointer!
3}
Why: Elem() panics if called on non-pointer, non-interface types.
6. Avoid Reflection in Hot Paths
β Correct: Reflection at initialization, direct access in hot path
1type FastAccessor struct {
2 getters []func(any) any
3}
4
5func NewAccessor(t reflect.Type) *FastAccessor {
6 // Reflection once at init
7 getters := make([]func(any) any, t.NumField())
8 for i := 0; i < t.NumField(); i++ {
9 field := t.Field(i)
10 getters[i] = func(obj any) any {
11 return reflect.ValueOf(obj).Field(i).Interface()
12 }
13 }
14 return &FastAccessor{getters: getters}
15}
16
17func Get(obj any, index int) any {
18 return a.getters[index](obj) // No reflection here!
19}
β Wrong: Reflection in tight loop
1func ProcessMillions(items []any) {
2 for _, item := range items { // Called 1M times
3 t := reflect.TypeOf(item) // SLOW!
4 for i := 0; i < t.NumField(); i++ {
5 field := reflect.ValueOf(item).Field(i) // VERY SLOW!
6 }
7 }
8}
Why: Reflection in loops = 10-100x slower. Do reflection once, cache results.
7. Document Why Reflection is Necessary
β Correct: Clear documentation
1// MapToStruct uses reflection to populate struct fields from map keys.
2// This is necessary because we need to handle arbitrary struct types
3// passed by users at runtime. Performance: ~100ns per field.
4//
5// Alternative considered: Code generation would require users to run
6// go generate, adding build complexity.
7func MapToStruct(m map[string]any, dest any) error {
8 // Reflection implementation...
9}
β Wrong: No explanation
1func MapToStruct(m map[string]any, dest any) error {
2 // Why reflection? Could we use generics? No comments!
3}
Why: Future maintainers need to know why reflection was chosen over alternatives.
Common Pitfalls
1. Forgetting Elem() for Pointers
β Problem: Modifying pointer value fails
1func SetName(user *User, name string) {
2 v := reflect.ValueOf(user) // v is pointer
3 v.FieldByName("Name").SetString(name) // PANIC: can't set pointer!
4}
β Solution: Use Elem() to get pointed-to value
1func SetName(user *User, name string) {
2 v := reflect.ValueOf(user).Elem() // Dereference pointer
3 v.FieldByName("Name").SetString(name) // Works!
4}
Why: Reflection on pointers requires .Elem() to access the underlying value.
2. Type Mismatches in Set()
β Problem: Setting wrong type panics
1v := reflect.ValueOf(&user).Elem()
2v.FieldByName("Age").Set(reflect.ValueOf("30")) // PANIC: string != int!
β Solution: Check and convert types
1func SetFieldSafe(v reflect.Value, fieldName string, value any) error {
2 field := v.FieldByName(fieldName)
3 if !field.IsValid() {
4 return fmt.Errorf("field not found")
5 }
6
7 val := reflect.ValueOf(value)
8 if !val.Type().AssignableTo(field.Type()) {
9 // Try converting
10 if val.Type().ConvertibleTo(field.Type()) {
11 val = val.Convert(field.Type())
12 } else {
13 return fmt.Errorf("type mismatch: %v != %v", val.Type(), field.Type())
14 }
15 }
16
17 field.Set(val)
18 return nil
19}
Why: Set() requires exact type match. Check with AssignableTo() or ConvertibleTo().
3. Ignoring Unexported Fields
β Problem: Can't set private fields
1type User struct {
2 name string // unexported
3}
4
5func SetName(user *User) {
6 v := reflect.ValueOf(user).Elem()
7 v.FieldByName("name").SetString("Alice") // PANIC: can't set unexported!
8}
β Solution: Use exported fields or unsafe package
1type User struct {
2 Name string // Exported - reflection works
3}
4
5// Or use unsafe
6import "unsafe"
7
8func SetNameUnsafe(user *User) {
9 v := reflect.ValueOf(user).Elem()
10 field := v.FieldByName("name")
11
12 // Use unsafe to bypass export check
13 reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).
14 Elem().SetString("Alice")
15}
Why: Reflection respects Go's visibility rules. Unexported fields can't be set.
4. Not Checking IsValid()
β Problem: Operating on invalid values
1v := reflect.ValueOf(user)
2name := v.FieldByName("InvalidField") // Returns invalid value
3name.SetString("Alice") // PANIC!
β Solution: Always check IsValid()
1v := reflect.ValueOf(user)
2name := v.FieldByName("Name")
3if !name.IsValid() {
4 return fmt.Errorf("field not found")
5}
6if !name.CanSet() {
7 return fmt.Errorf("field cannot be set")
8}
9name.SetString("Alice")
Why: Invalid values panic on any operation. Always check IsValid() and CanSet().
5. Calling Method with Wrong Arguments
β Problem: Argument type/count mismatch
1method := reflect.ValueOf(obj).MethodByName("Process")
2method.Call([]reflect.Value{reflect.ValueOf("wrong")}) // PANIC if expects int!
β Solution: Validate method signature
1func CallMethod(obj any, methodName string, args ...any) {
2 v := reflect.ValueOf(obj)
3 method := v.MethodByName(methodName)
4 if !method.IsValid() {
5 return nil, fmt.Errorf("method %s not found", methodName)
6 }
7
8 methodType := method.Type()
9 if len(args) != methodType.NumIn() {
10 return nil, fmt.Errorf("expected %d args, got %d", methodType.NumIn(), len(args))
11 }
12
13 // Convert args to reflect.Value and check types
14 in := make([]reflect.Value, len(args))
15 for i, arg := range args {
16 in[i] = reflect.ValueOf(arg)
17 if !in[i].Type().AssignableTo(methodType.In(i)) {
18 return nil, fmt.Errorf("arg %d: type mismatch", i)
19 }
20 }
21
22 results := method.Call(in)
23 out := make([]any, len(results))
24 for i, r := range results {
25 out[i] = r.Interface()
26 }
27 return out, nil
28}
Why: Method calls require exact argument types and counts. Validate before calling.
6. Interface{} vs reflect.Value Confusion
β Problem: Mixing interfaces and reflection
1func Process(v reflect.Value) {
2 if v == nil { // WRONG: comparing reflect.Value to nil
3 return
4 }
5}
β Solution: Use IsValid() for reflect.Value
1func Process(v reflect.Value) {
2 if !v.IsValid() { // Correct check
3 return
4 }
5 if v.Kind() == reflect.Ptr && v.IsNil() { // Check pointer nil
6 return
7 }
8}
Why: reflect.Value is never nil. Use IsValid() to check zero value.
7. Modifying Read-Only Values
β Problem: Trying to modify struct passed by value
1func Modify(obj any) {
2 v := reflect.ValueOf(obj) // obj is copy!
3 v.FieldByName("Name").SetString("Alice") // PANIC: not addressable!
4}
5
6Modify(user) // user passed by value
β Solution: Pass pointer
1func Modify(obj any) {
2 v := reflect.ValueOf(obj)
3 if v.Kind() != reflect.Ptr {
4 panic("obj must be pointer")
5 }
6 v.Elem().FieldByName("Name").SetString("Alice")
7}
8
9Modify(&user) // Pass pointer
Why: Can only modify addressable values. Pass pointers for modification.
8. DeepEqual Performance in Hot Paths
β Problem: Using DeepEqual in loops
1func FindDuplicates(items []any) {
2 for i := 0; i < len(items); i++ {
3 for j := i + 1; j < len(items); j++ {
4 if reflect.DeepEqual(items[i], items[j]) { // SLOW: O(nΒ²)
5 // ...
6 }
7 }
8 }
9}
β Solution: Use hash maps with custom equality
1func FindDuplicates[T comparable](items []T) {
2 seen := make(map[T]struct{})
3 for _, item := range items {
4 if _, exists := seen[item]; exists { // O(1) lookup
5 // Found duplicate
6 }
7 seen[item] = struct{}{}
8 }
9}
Why: DeepEqual is 75x slower than direct comparison. Use only when necessary.
9. Not Handling Recursive Structures
β Problem: Infinite recursion
1type Node struct {
2 Value int
3 Next *Node
4}
5
6func PrintFields(v any) {
7 val := reflect.ValueOf(v)
8 for i := 0; i < val.NumField(); i++ {
9 field := val.Field(i)
10 if field.Kind() == reflect.Ptr {
11 PrintFields(field.Interface()) // Infinite loop if circular!
12 }
13 }
14}
β Solution: Track visited pointers
1func PrintFields(v any, visited map[uintptr]bool) {
2 val := reflect.ValueOf(v)
3 if val.Kind() == reflect.Ptr {
4 ptr := val.Pointer()
5 if visited[ptr] {
6 return // Already visited
7 }
8 visited[ptr] = true
9 val = val.Elem()
10 }
11
12 for i := 0; i < val.NumField(); i++ {
13 field := val.Field(i)
14 if field.Kind() == reflect.Ptr && !field.IsNil() {
15 PrintFields(field.Interface(), visited)
16 }
17 }
18}
Why: Circular references cause infinite recursion. Track visited pointers.
10. Forgetting to Handle Errors from Interface()
β Problem: Panicking on Interface() call
1func GetValue(v reflect.Value) any {
2 return v.Interface() // PANIC if v is unexported field!
3}
β Solution: Check CanInterface()
1func GetValue(v reflect.Value) {
2 if !v.CanInterface() {
3 return nil, fmt.Errorf("cannot interface")
4 }
5 return v.Interface(), nil
6}
Why: Interface() panics on unexported fields. Check CanInterface() first.
Production Pattern - Mini ORM Implementation
Building a simple but functional ORM using reflection:
1package main
2
3import (
4 "database/sql"
5 "errors"
6 "fmt"
7 "reflect"
8 "strings"
9
10 _ "github.com/mattn/go-sqlite3"
11)
12
13// DB wraps database connection with ORM functionality
14type DB struct {
15 conn *sql.DB
16}
17
18// NewDB creates a new database connection
19func NewDB(driver, dsn string) {
20 conn, err := sql.Open(driver, dsn)
21 if err != nil {
22 return nil, err
23 }
24 return &DB{conn: conn}, nil
25}
26
27// CreateTable generates CREATE TABLE statement from struct
28func CreateTable(model interface{}) error {
29 t := reflect.TypeOf(model)
30 if t.Kind() == reflect.Ptr {
31 t = t.Elem()
32 }
33
34 if t.Kind() != reflect.Struct {
35 return errors.New("model must be struct")
36 }
37
38 tableName := toSnakeCase(t.Name())
39 var columns []string
40
41 for i := 0; i < t.NumField(); i++ {
42 field := t.Field(i)
43 columnName := toSnakeCase(field.Name)
44 columnType := getSQLType(field.Type)
45
46 // Check for primary key tag
47 tag := field.Tag.Get("db")
48 if strings.Contains(tag, "primary_key") {
49 columnType += " PRIMARY KEY"
50 }
51 if strings.Contains(tag, "autoincrement") {
52 columnType += " AUTOINCREMENT"
53 }
54 if strings.Contains(tag, "not_null") {
55 columnType += " NOT NULL"
56 }
57
58 columns = append(columns, fmt.Sprintf("%s %s", columnName, columnType))
59 }
60
61 query := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s", tableName, strings.Join(columns, ", "))
62 _, err := db.conn.Exec(query)
63 return err
64}
65
66// Insert adds a new record
67func Insert(model interface{}) error {
68 v := reflect.ValueOf(model)
69 if v.Kind() == reflect.Ptr {
70 v = v.Elem()
71 }
72 t := v.Type()
73
74 tableName := toSnakeCase(t.Name())
75 var columns []string
76 var placeholders []string
77 var values []interface{}
78
79 for i := 0; i < t.NumField(); i++ {
80 field := t.Field(i)
81 fieldValue := v.Field(i)
82
83 // Skip auto-increment fields
84 tag := field.Tag.Get("db")
85 if strings.Contains(tag, "autoincrement") {
86 continue
87 }
88
89 columns = append(columns, toSnakeCase(field.Name))
90 placeholders = append(placeholders, "?")
91 values = append(values, fieldValue.Interface())
92 }
93
94 query := fmt.Sprintf(
95 "INSERT INTO %s VALUES",
96 tableName,
97 strings.Join(columns, ", "),
98 strings.Join(placeholders, ", "),
99 )
100
101 _, err := db.conn.Exec(query, values...)
102 return err
103}
104
105// Find retrieves a record by ID
106func Find(model interface{}, id interface{}) error {
107 v := reflect.ValueOf(model)
108 if v.Kind() != reflect.Ptr {
109 return errors.New("model must be pointer to struct")
110 }
111 v = v.Elem()
112 t := v.Type()
113
114 tableName := toSnakeCase(t.Name())
115 var columns []string
116
117 for i := 0; i < t.NumField(); i++ {
118 columns = append(columns, toSnakeCase(t.Field(i).Name))
119 }
120
121 query := fmt.Sprintf(
122 "SELECT %s FROM %s WHERE id = ?",
123 strings.Join(columns, ", "),
124 tableName,
125 )
126
127 row := db.conn.QueryRow(query, id)
128
129 // Prepare scan targets
130 scanTargets := make([]interface{}, v.NumField())
131 for i := 0; i < v.NumField(); i++ {
132 scanTargets[i] = v.Field(i).Addr().Interface()
133 }
134
135 return row.Scan(scanTargets...)
136}
137
138// FindAll retrieves all records
139func FindAll(models interface{}) error {
140 v := reflect.ValueOf(models)
141 if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice {
142 return errors.New("models must be pointer to slice")
143 }
144
145 sliceValue := v.Elem()
146 elemType := sliceValue.Type().Elem()
147
148 if elemType.Kind() == reflect.Ptr {
149 elemType = elemType.Elem()
150 }
151
152 tableName := toSnakeCase(elemType.Name())
153 var columns []string
154
155 for i := 0; i < elemType.NumField(); i++ {
156 columns = append(columns, toSnakeCase(elemType.Field(i).Name))
157 }
158
159 query := fmt.Sprintf("SELECT %s FROM %s", strings.Join(columns, ", "), tableName)
160 rows, err := db.conn.Query(query)
161 if err != nil {
162 return err
163 }
164 defer rows.Close()
165
166 for rows.Next() {
167 // Create new instance
168 newElem := reflect.New(elemType)
169
170 // Prepare scan targets
171 scanTargets := make([]interface{}, elemType.NumField())
172 for i := 0; i < elemType.NumField(); i++ {
173 scanTargets[i] = newElem.Elem().Field(i).Addr().Interface()
174 }
175
176 if err := rows.Scan(scanTargets...); err != nil {
177 return err
178 }
179
180 // Append to slice
181 if sliceValue.Type().Elem().Kind() == reflect.Ptr {
182 sliceValue.Set(reflect.Append(sliceValue, newElem))
183 } else {
184 sliceValue.Set(reflect.Append(sliceValue, newElem.Elem()))
185 }
186 }
187
188 return rows.Err()
189}
190
191// Update modifies an existing record
192func Update(model interface{}) error {
193 v := reflect.ValueOf(model)
194 if v.Kind() == reflect.Ptr {
195 v = v.Elem()
196 }
197 t := v.Type()
198
199 tableName := toSnakeCase(t.Name())
200 var setParts []string
201 var values []interface{}
202 var id interface{}
203
204 for i := 0; i < t.NumField(); i++ {
205 field := t.Field(i)
206 fieldValue := v.Field(i)
207 columnName := toSnakeCase(field.Name)
208
209 // Store ID for WHERE clause
210 if columnName == "id" {
211 id = fieldValue.Interface()
212 continue
213 }
214
215 setParts = append(setParts, fmt.Sprintf("%s = ?", columnName))
216 values = append(values, fieldValue.Interface())
217 }
218
219 values = append(values, id)
220 query := fmt.Sprintf(
221 "UPDATE %s SET %s WHERE id = ?",
222 tableName,
223 strings.Join(setParts, ", "),
224 )
225
226 _, err := db.conn.Exec(query, values...)
227 return err
228}
229
230// Delete removes a record
231func Delete(model interface{}, id interface{}) error {
232 t := reflect.TypeOf(model)
233 if t.Kind() == reflect.Ptr {
234 t = t.Elem()
235 }
236
237 tableName := toSnakeCase(t.Name())
238 query := fmt.Sprintf("DELETE FROM %s WHERE id = ?", tableName)
239
240 _, err := db.conn.Exec(query, id)
241 return err
242}
243
244// Helper: Convert CamelCase to snake_case
245func toSnakeCase(s string) string {
246 var result strings.Builder
247 for i, r := range s {
248 if i > 0 && r >= 'A' && r <= 'Z' {
249 result.WriteRune('_')
250 }
251 result.WriteRune(r)
252 }
253 return strings.ToLower(result.String())
254}
255
256// Helper: Map Go types to SQL types
257func getSQLType(t reflect.Type) string {
258 switch t.Kind() {
259 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
260 return "INTEGER"
261 case reflect.String:
262 return "TEXT"
263 case reflect.Bool:
264 return "BOOLEAN"
265 case reflect.Float32, reflect.Float64:
266 return "REAL"
267 default:
268 return "TEXT"
269 }
270}
271
272// Example models
273type User struct {
274 ID int `db:"primary_key,autoincrement"`
275 Name string `db:"not_null"`
276 Email string `db:"not_null"`
277 Age int
278}
279
280type Product struct {
281 ID int `db:"primary_key,autoincrement"`
282 Name string `db:"not_null"`
283 Price float64 `db:"not_null"`
284}
285
286func main() {
287 // Open database
288 db, err := NewDB("sqlite3", ":memory:")
289 if err != nil {
290 panic(err)
291 }
292
293 // Create tables
294 if err := db.CreateTable(User{}); err != nil {
295 panic(err)
296 }
297 if err := db.CreateTable(Product{}); err != nil {
298 panic(err)
299 }
300
301 // Insert users
302 alice := User{Name: "Alice", Email: "alice@example.com", Age: 30}
303 bob := User{Name: "Bob", Email: "bob@example.com", Age: 25}
304
305 db.Insert(&alice)
306 db.Insert(&bob)
307
308 // Find by ID
309 var foundUser User
310 if err := db.Find(&foundUser, 1); err == nil {
311 fmt.Printf("Found user: %+v\n", foundUser)
312 }
313
314 // Find all users
315 var users []User
316 if err := db.FindAll(&users); err == nil {
317 fmt.Println("\nAll users:")
318 for _, user := range users {
319 fmt.Printf("- %s, Age: %d\n", user.Name, user.Email, user.Age)
320 }
321 }
322
323 // Update user
324 foundUser.Age = 31
325 if err := db.Update(&foundUser); err != nil {
326 fmt.Println("Update error:", err)
327 }
328
329 // Delete user
330 if err := db.Delete(User{}, 2); err != nil {
331 fmt.Println("Delete error:", err)
332 }
333
334 // Verify deletion
335 var remainingUsers []User
336 db.FindAll(&remainingUsers)
337 fmt.Printf("\nRemaining users: %d\n", len(remainingUsers))
338}
Production Pattern - Struct Validation Library
A comprehensive validation library using reflection and struct tags:
1package main
2
3import (
4 "errors"
5 "fmt"
6 "reflect"
7 "regexp"
8 "strconv"
9 "strings"
10)
11
12// ValidationError represents a validation failure
13type ValidationError struct {
14 Field string
15 Tag string
16 Message string
17}
18
19func Error() string {
20 return fmt.Sprintf("%s: %s", e.Field, e.Message)
21}
22
23// ValidationErrors is a collection of validation errors
24type ValidationErrors []ValidationError
25
26func Error() string {
27 var msgs []string
28 for _, err := range ve {
29 msgs = append(msgs, err.Error())
30 }
31 return strings.Join(msgs, "; ")
32}
33
34// Validator performs struct validation
35type Validator struct {
36 validators map[string]ValidatorFunc
37}
38
39// ValidatorFunc is a validation function
40type ValidatorFunc func(field reflect.Value, param string) error
41
42// NewValidator creates a new validator with built-in rules
43func NewValidator() *Validator {
44 v := &Validator{
45 validators: make(map[string]ValidatorFunc),
46 }
47
48 // Register built-in validators
49 v.RegisterValidator("required", validateRequired)
50 v.RegisterValidator("min", validateMin)
51 v.RegisterValidator("max", validateMax)
52 v.RegisterValidator("len", validateLen)
53 v.RegisterValidator("email", validateEmail)
54 v.RegisterValidator("url", validateURL)
55 v.RegisterValidator("alpha", validateAlpha)
56 v.RegisterValidator("numeric", validateNumeric)
57 v.RegisterValidator("oneof", validateOneOf)
58 v.RegisterValidator("gt", validateGreaterThan)
59 v.RegisterValidator("gte", validateGreaterThanOrEqual)
60 v.RegisterValidator("lt", validateLessThan)
61 v.RegisterValidator("lte", validateLessThanOrEqual)
62
63 return v
64}
65
66// RegisterValidator adds a custom validation function
67func RegisterValidator(tag string, fn ValidatorFunc) {
68 v.validators[tag] = fn
69}
70
71// Validate validates a struct
72func Validate(s interface{}) error {
73 val := reflect.ValueOf(s)
74 if val.Kind() == reflect.Ptr {
75 val = val.Elem()
76 }
77
78 if val.Kind() != reflect.Struct {
79 return errors.New("validate: input must be struct or pointer to struct")
80 }
81
82 var errs ValidationErrors
83
84 typ := val.Type()
85 for i := 0; i < val.NumField(); i++ {
86 field := val.Field(i)
87 fieldType := typ.Field(i)
88
89 // Skip unexported fields
90 if !field.CanInterface() {
91 continue
92 }
93
94 // Get validation tag
95 tag := fieldType.Tag.Get("validate")
96 if tag == "" || tag == "-" {
97 continue
98 }
99
100 // Parse validation rules
101 rules := strings.Split(tag, ",")
102 for _, rule := range rules {
103 parts := strings.SplitN(rule, "=", 2)
104 ruleName := parts[0]
105 ruleParam := ""
106 if len(parts) == 2 {
107 ruleParam = parts[1]
108 }
109
110 // Find and execute validator
111 if validator, exists := v.validators[ruleName]; exists {
112 if err := validator(field, ruleParam); err != nil {
113 errs = append(errs, ValidationError{
114 Field: fieldType.Name,
115 Tag: ruleName,
116 Message: err.Error(),
117 })
118 }
119 }
120 }
121 }
122
123 if len(errs) > 0 {
124 return errs
125 }
126 return nil
127}
128
129// Built-in validators
130
131func validateRequired(field reflect.Value, param string) error {
132 if field.Kind() == reflect.String && field.Len() == 0 {
133 return errors.New("field is required")
134 }
135 if field.Kind() == reflect.Slice && field.Len() == 0 {
136 return errors.New("field is required")
137 }
138 if field.IsZero() {
139 return errors.New("field is required")
140 }
141 return nil
142}
143
144func validateMin(field reflect.Value, param string) error {
145 min, err := strconv.Atoi(param)
146 if err != nil {
147 return fmt.Errorf("invalid min parameter: %s", param)
148 }
149
150 switch field.Kind() {
151 case reflect.String:
152 if field.Len() < min {
153 return fmt.Errorf("must be at least %d characters", min)
154 }
155 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
156 if field.Int() < int64(min) {
157 return fmt.Errorf("must be at least %d", min)
158 }
159 case reflect.Slice, reflect.Array:
160 if field.Len() < min {
161 return fmt.Errorf("must have at least %d items", min)
162 }
163 }
164 return nil
165}
166
167func validateMax(field reflect.Value, param string) error {
168 max, err := strconv.Atoi(param)
169 if err != nil {
170 return fmt.Errorf("invalid max parameter: %s", param)
171 }
172
173 switch field.Kind() {
174 case reflect.String:
175 if field.Len() > max {
176 return fmt.Errorf("must be at most %d characters", max)
177 }
178 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
179 if field.Int() > int64(max) {
180 return fmt.Errorf("must be at most %d", max)
181 }
182 case reflect.Slice, reflect.Array:
183 if field.Len() > max {
184 return fmt.Errorf("must have at most %d items", max)
185 }
186 }
187 return nil
188}
189
190func validateLen(field reflect.Value, param string) error {
191 length, err := strconv.Atoi(param)
192 if err != nil {
193 return fmt.Errorf("invalid len parameter: %s", param)
194 }
195
196 if field.Kind() == reflect.String && field.Len() != length {
197 return fmt.Errorf("must be exactly %d characters", length)
198 }
199 return nil
200}
201
202func validateEmail(field reflect.Value, param string) error {
203 if field.Kind() != reflect.String {
204 return errors.New("email validation only works on strings")
205 }
206
207 emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
208 if !emailRegex.MatchString(field.String()) {
209 return errors.New("must be a valid email address")
210 }
211 return nil
212}
213
214func validateURL(field reflect.Value, param string) error {
215 if field.Kind() != reflect.String {
216 return errors.New("url validation only works on strings")
217 }
218
219 urlRegex := regexp.MustCompile(`^https?://[^\s/$.?#].[^\s]*$`)
220 if !urlRegex.MatchString(field.String()) {
221 return errors.New("must be a valid URL")
222 }
223 return nil
224}
225
226func validateAlpha(field reflect.Value, param string) error {
227 if field.Kind() != reflect.String {
228 return errors.New("alpha validation only works on strings")
229 }
230
231 alphaRegex := regexp.MustCompile(`^[a-zA-Z]+$`)
232 if !alphaRegex.MatchString(field.String()) {
233 return errors.New("must contain only letters")
234 }
235 return nil
236}
237
238func validateNumeric(field reflect.Value, param string) error {
239 if field.Kind() != reflect.String {
240 return errors.New("numeric validation only works on strings")
241 }
242
243 numericRegex := regexp.MustCompile(`^[0-9]+$`)
244 if !numericRegex.MatchString(field.String()) {
245 return errors.New("must contain only numbers")
246 }
247 return nil
248}
249
250func validateOneOf(field reflect.Value, param string) error {
251 if field.Kind() != reflect.String {
252 return errors.New("oneof validation only works on strings")
253 }
254
255 allowed := strings.Split(param, " ")
256 value := field.String()
257 for _, a := range allowed {
258 if value == a {
259 return nil
260 }
261 }
262 return fmt.Errorf("must be one of: %s", param)
263}
264
265func validateGreaterThan(field reflect.Value, param string) error {
266 threshold, err := strconv.ParseFloat(param, 64)
267 if err != nil {
268 return fmt.Errorf("invalid gt parameter: %s", param)
269 }
270
271 var value float64
272 switch field.Kind() {
273 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
274 value = float64(field.Int())
275 case reflect.Float32, reflect.Float64:
276 value = field.Float()
277 default:
278 return errors.New("gt only works on numeric types")
279 }
280
281 if value <= threshold {
282 return fmt.Errorf("must be greater than %s", param)
283 }
284 return nil
285}
286
287func validateGreaterThanOrEqual(field reflect.Value, param string) error {
288 threshold, err := strconv.ParseFloat(param, 64)
289 if err != nil {
290 return fmt.Errorf("invalid gte parameter: %s", param)
291 }
292
293 var value float64
294 switch field.Kind() {
295 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
296 value = float64(field.Int())
297 case reflect.Float32, reflect.Float64:
298 value = field.Float()
299 default:
300 return errors.New("gte only works on numeric types")
301 }
302
303 if value < threshold {
304 return fmt.Errorf("must be at least %s", param)
305 }
306 return nil
307}
308
309func validateLessThan(field reflect.Value, param string) error {
310 threshold, err := strconv.ParseFloat(param, 64)
311 if err != nil {
312 return fmt.Errorf("invalid lt parameter: %s", param)
313 }
314
315 var value float64
316 switch field.Kind() {
317 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
318 value = float64(field.Int())
319 case reflect.Float32, reflect.Float64:
320 value = field.Float()
321 default:
322 return errors.New("lt only works on numeric types")
323 }
324
325 if value >= threshold {
326 return fmt.Errorf("must be less than %s", param)
327 }
328 return nil
329}
330
331func validateLessThanOrEqual(field reflect.Value, param string) error {
332 threshold, err := strconv.ParseFloat(param, 64)
333 if err != nil {
334 return fmt.Errorf("invalid lte parameter: %s", param)
335 }
336
337 var value float64
338 switch field.Kind() {
339 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
340 value = float64(field.Int())
341 case reflect.Float32, reflect.Float64:
342 value = field.Float()
343 default:
344 return errors.New("lte only works on numeric types")
345 }
346
347 if value > threshold {
348 return fmt.Errorf("must be at most %s", param)
349 }
350 return nil
351}
352
353// Example usage
354type CreateUserRequest struct {
355 Username string `validate:"required,min=3,max=20,alpha"`
356 Email string `validate:"required,email"`
357 Age int `validate:"required,gte=18,lte=120"`
358 Password string `validate:"required,min=8"`
359 Role string `validate:"required,oneof=admin user guest"`
360 Website string `validate:"url"`
361}
362
363func main() {
364 validator := NewValidator()
365
366 // Valid request
367 validReq := CreateUserRequest{
368 Username: "alice",
369 Email: "alice@example.com",
370 Age: 30,
371 Password: "securepass123",
372 Role: "user",
373 Website: "https://example.com",
374 }
375
376 if err := validator.Validate(validReq); err != nil {
377 fmt.Println("Validation failed:", err)
378 } else {
379 fmt.Println("β Valid request")
380 }
381
382 // Invalid request
383 invalidReq := CreateUserRequest{
384 Username: "ab", // Too short
385 Email: "invalid-email", // Invalid email
386 Age: 15, // Too young
387 Password: "short", // Too short
388 Role: "superuser", // Not in oneof list
389 Website: "not-a-url", // Invalid URL
390 }
391
392 if err := validator.Validate(invalidReq); err != nil {
393 fmt.Println("\nValidation errors:")
394 if errs, ok := err.(ValidationErrors); ok {
395 for _, e := range errs {
396 fmt.Printf("- %s: %s\n", e.Field, e.Message)
397 }
398 }
399 }
400
401 // Custom validator
402 validator.RegisterValidator("strong_password", func(field reflect.Value, param string) error {
403 if field.Kind() != reflect.String {
404 return errors.New("strong_password only works on strings")
405 }
406
407 password := field.String()
408 hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
409 hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
410 hasDigit := regexp.MustCompile(`[0-9]`).MatchString(password)
411 hasSpecial := regexp.MustCompile(`[!@#$%^&*]`).MatchString(password)
412
413 if !hasUpper || !hasLower || !hasDigit || !hasSpecial {
414 return errors.New("must contain uppercase, lowercase, digit, and special character")
415 }
416 return nil
417 })
418
419 type SecureRequest struct {
420 Password string `validate:"required,min=8,strong_password"`
421 }
422
423 secureReq := SecureRequest{Password: "Weak123"}
424 if err := validator.Validate(secureReq); err != nil {
425 fmt.Println("\nCustom validation:", err)
426 }
427}
Production Pattern - Dynamic JSON Marshaling
Advanced JSON handling with reflection for flexible APIs:
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "reflect"
7 "strings"
8 "time"
9)
10
11// DynamicMarshaler provides custom JSON marshaling with dynamic fields
12type DynamicMarshaler struct {
13 includeEmpty bool
14 omitFields map[string]bool
15 fieldAliases map[string]string
16 transformers map[string]func(interface{}) interface{}
17}
18
19// NewDynamicMarshaler creates a new dynamic marshaler
20func NewDynamicMarshaler() *DynamicMarshaler {
21 return &DynamicMarshaler{
22 omitFields: make(map[string]bool),
23 fieldAliases: make(map[string]string),
24 transformers: make(map[string]func(interface{}) interface{}),
25 }
26}
27
28// WithIncludeEmpty includes empty fields
29func WithIncludeEmpty() *DynamicMarshaler {
30 dm.includeEmpty = true
31 return dm
32}
33
34// OmitField excludes a field from output
35func OmitField(fieldName string) *DynamicMarshaler {
36 dm.omitFields[fieldName] = true
37 return dm
38}
39
40// AliasField renames a field in output
41func AliasField(original, alias string) *DynamicMarshaler {
42 dm.fieldAliases[original] = alias
43 return dm
44}
45
46// TransformField applies a transformation to a field
47func TransformField(fieldName string, fn func(interface{}) interface{}) *DynamicMarshaler {
48 dm.transformers[fieldName] = fn
49 return dm
50}
51
52// Marshal converts struct to JSON with dynamic rules
53func Marshal(v interface{}) {
54 val := reflect.ValueOf(v)
55 if val.Kind() == reflect.Ptr {
56 val = val.Elem()
57 }
58
59 if val.Kind() != reflect.Struct {
60 return json.Marshal(v)
61 }
62
63 result := make(map[string]interface{})
64 typ := val.Type()
65
66 for i := 0; i < val.NumField(); i++ {
67 field := val.Field(i)
68 fieldType := typ.Field(i)
69
70 // Skip unexported fields
71 if !field.CanInterface() {
72 continue
73 }
74
75 fieldName := fieldType.Name
76
77 // Check if field should be omitted
78 if dm.omitFields[fieldName] {
79 continue
80 }
81
82 // Skip empty values if not including empty
83 if !dm.includeEmpty && isEmptyValue(field) {
84 continue
85 }
86
87 // Get JSON name from tag or use field name
88 jsonTag := fieldType.Tag.Get("json")
89 if jsonTag != "" && jsonTag != "-" {
90 parts := strings.Split(jsonTag, ",")
91 if parts[0] != "" {
92 fieldName = parts[0]
93 }
94 }
95
96 // Apply alias if exists
97 if alias, exists := dm.fieldAliases[fieldType.Name]; exists {
98 fieldName = alias
99 }
100
101 // Get field value
102 fieldValue := field.Interface()
103
104 // Apply transformer if exists
105 if transformer, exists := dm.transformers[fieldType.Name]; exists {
106 fieldValue = transformer(fieldValue)
107 }
108
109 result[fieldName] = fieldValue
110 }
111
112 return json.MarshalIndent(result, "", " ")
113}
114
115// Helper to check if value is empty
116func isEmptyValue(v reflect.Value) bool {
117 switch v.Kind() {
118 case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
119 return v.Len() == 0
120 case reflect.Bool:
121 return !v.Bool()
122 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
123 return v.Int() == 0
124 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
125 return v.Uint() == 0
126 case reflect.Float32, reflect.Float64:
127 return v.Float() == 0
128 case reflect.Interface, reflect.Ptr:
129 return v.IsNil()
130 }
131 return false
132}
133
134// DynamicUnmarshaler provides custom JSON unmarshaling
135type DynamicUnmarshaler struct {
136 fieldMappings map[string]string
137 validators map[string]func(interface{}) error
138}
139
140// NewDynamicUnmarshaler creates a new dynamic unmarshaler
141func NewDynamicUnmarshaler() *DynamicUnmarshaler {
142 return &DynamicUnmarshaler{
143 fieldMappings: make(map[string]string),
144 validators: make(map[string]func(interface{}) error),
145 }
146}
147
148// MapField maps JSON field to struct field
149func MapField(jsonField, structField string) *DynamicUnmarshaler {
150 du.fieldMappings[jsonField] = structField
151 return du
152}
153
154// ValidateField adds validation for a field
155func ValidateField(fieldName string, fn func(interface{}) error) *DynamicUnmarshaler {
156 du.validators[fieldName] = fn
157 return du
158}
159
160// Unmarshal converts JSON to struct with dynamic rules
161func Unmarshal(data []byte, v interface{}) error {
162 val := reflect.ValueOf(v)
163 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
164 return fmt.Errorf("v must be pointer to struct")
165 }
166 val = val.Elem()
167 typ := val.Type()
168
169 // First unmarshal to map
170 var rawData map[string]interface{}
171 if err := json.Unmarshal(data, &rawData); err != nil {
172 return err
173 }
174
175 // Map fields
176 for jsonField, value := range rawData {
177 // Check for field mapping
178 structField := jsonField
179 if mapped, exists := du.fieldMappings[jsonField]; exists {
180 structField = mapped
181 }
182
183 // Find struct field
184 field, found := typ.FieldByName(structField)
185 if !found {
186 continue
187 }
188
189 fieldValue := val.FieldByName(structField)
190 if !fieldValue.CanSet() {
191 continue
192 }
193
194 // Validate if validator exists
195 if validator, exists := du.validators[structField]; exists {
196 if err := validator(value); err != nil {
197 return fmt.Errorf("validation failed for %s: %w", structField, err)
198 }
199 }
200
201 // Set value
202 if err := setField(fieldValue, value); err != nil {
203 return fmt.Errorf("failed to set %s: %w", structField, err)
204 }
205 }
206
207 return nil
208}
209
210// Helper to set field value
211func setField(field reflect.Value, value interface{}) error {
212 valValue := reflect.ValueOf(value)
213
214 // Handle type conversion
215 if valValue.Type().AssignableTo(field.Type()) {
216 field.Set(valValue)
217 return nil
218 }
219
220 // Convert numbers
221 switch field.Kind() {
222 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
223 if num, ok := value.(float64); ok {
224 field.SetInt(int64(num))
225 return nil
226 }
227 case reflect.String:
228 field.SetString(fmt.Sprint(value))
229 return nil
230 }
231
232 return fmt.Errorf("cannot convert %T to %s", value, field.Type())
233}
234
235// Example models
236type APIResponse struct {
237 ID int `json:"id"`
238 Username string `json:"username"`
239 Email string `json:"email"`
240 CreatedAt time.Time `json:"created_at"`
241 IsActive bool `json:"is_active"`
242 Metadata map[string]string `json:"metadata"`
243 Tags []string `json:"tags"`
244}
245
246func main() {
247 // Create sample data
248 response := APIResponse{
249 ID: 123,
250 Username: "alice",
251 Email: "alice@example.com",
252 CreatedAt: time.Now(),
253 IsActive: true,
254 Metadata: map[string]string{"role": "admin", "level": "5"},
255 Tags: []string{"vip", "verified"},
256 }
257
258 fmt.Println("=== Dynamic Marshaling Examples ===\n")
259
260 // Example 1: Basic marshaling
261 marshaler := NewDynamicMarshaler()
262 data, _ := marshaler.Marshal(response)
263 fmt.Println("1. Basic marshaling:")
264 fmt.Println(string(data))
265
266 // Example 2: Omit sensitive fields
267 fmt.Println("\n2. Omit email:")
268 marshaler = NewDynamicMarshaler().OmitField("Email")
269 data, _ = marshaler.Marshal(response)
270 fmt.Println(string(data))
271
272 // Example 3: Field aliasing
273 fmt.Println("\n3. Rename fields:")
274 marshaler = NewDynamicMarshaler().
275 AliasField("Username", "user_name").
276 AliasField("CreatedAt", "registration_date")
277 data, _ = marshaler.Marshal(response)
278 fmt.Println(string(data))
279
280 // Example 4: Field transformation
281 fmt.Println("\n4. Transform fields:")
282 marshaler = NewDynamicMarshaler().
283 TransformField("Email", func(v interface{}) interface{} {
284 email := v.(string)
285 parts := strings.Split(email, "@")
286 if len(parts) == 2 {
287 return parts[0][:2] + "***@" + parts[1]
288 }
289 return email
290 }).
291 TransformField("CreatedAt", func(v interface{}) interface{} {
292 t := v.(time.Time)
293 return t.Format("2006-01-02")
294 })
295 data, _ = marshaler.Marshal(response)
296 fmt.Println(string(data))
297
298 // Example 5: Dynamic unmarshaling
299 fmt.Println("\n5. Dynamic unmarshaling:")
300 jsonData := []byte(`{
301 "user_id": 456,
302 "user_name": "bob",
303 "email_address": "bob@example.com",
304 "active": true
305 }`)
306
307 var parsedResponse APIResponse
308 unmarshaler := NewDynamicUnmarshaler().
309 MapField("user_id", "ID").
310 MapField("user_name", "Username").
311 MapField("email_address", "Email").
312 MapField("active", "IsActive").
313 ValidateField("ID", func(v interface{}) error {
314 if id, ok := v.(float64); ok && id < 0 {
315 return fmt.Errorf("ID must be positive")
316 }
317 return nil
318 })
319
320 if err := unmarshaler.Unmarshal(jsonData, &parsedResponse); err != nil {
321 fmt.Println("Unmarshal error:", err)
322 } else {
323 fmt.Printf("Parsed: ID=%d, Username=%s, Email=%s, Active=%v\n",
324 parsedResponse.ID, parsedResponse.Username, parsedResponse.Email, parsedResponse.IsActive)
325 }
326}
Advanced Reflection Patterns
Reflection-Based Type Coercion and Conversion
Converting between types at runtime is a common need in API handlers, configuration systems, and data transformers. Reflection enables flexible, automatic conversions:
1package main
2
3import (
4 "fmt"
5 "reflect"
6 "strconv"
7 "strings"
8)
9
10// TypeConverter provides runtime type conversion
11type TypeConverter struct {
12 converters map[string]func(interface{}) (interface{}, error)
13}
14
15// NewTypeConverter creates a new converter
16func NewTypeConverter() *TypeConverter {
17 tc := &TypeConverter{
18 converters: make(map[string]func(interface{}) (interface{}, error)),
19 }
20
21 // Register standard converters
22 tc.Register("int", func(v interface{}) (interface{}, error) {
23 switch val := v.(type) {
24 case int:
25 return val, nil
26 case float64:
27 return int(val), nil
28 case string:
29 i, err := strconv.Atoi(val)
30 return i, err
31 default:
32 return nil, fmt.Errorf("cannot convert %T to int", v)
33 }
34 })
35
36 tc.Register("string", func(v interface{}) (interface{}, error) {
37 return fmt.Sprint(v), nil
38 })
39
40 tc.Register("bool", func(v interface{}) (interface{}, error) {
41 switch val := v.(type) {
42 case bool:
43 return val, nil
44 case string:
45 return strings.ToLower(val) == "true", nil
46 default:
47 return nil, fmt.Errorf("cannot convert %T to bool", v)
48 }
49 })
50
51 return tc
52}
53
54// Register adds a custom converter
55func (tc *TypeConverter) Register(typeName string, converter func(interface{}) (interface{}, error)) {
56 tc.converters[typeName] = converter
57}
58
59// Convert performs type conversion
60func (tc *TypeConverter) Convert(value interface{}, targetType string) (interface{}, error) {
61 if converter, exists := tc.converters[targetType]; exists {
62 return converter(value)
63 }
64 return nil, fmt.Errorf("no converter for type %s", targetType)
65}
66
67// ConvertToType uses reflection to convert to target type
68func (tc *TypeConverter) ConvertToType(value interface{}, targetType reflect.Type) (interface{}, error) {
69 valRef := reflect.ValueOf(value)
70
71 // Try direct assignment
72 if valRef.Type().AssignableTo(targetType) {
73 return valRef.Convert(targetType).Interface(), nil
74 }
75
76 // Use registered converters
77 result, err := tc.Convert(value, targetType.Name())
78 if err == nil && result != nil {
79 return reflect.ValueOf(result).Convert(targetType).Interface(), nil
80 }
81
82 return nil, fmt.Errorf("cannot convert %T to %s", value, targetType.Name())
83}
84
85func main() {
86 converter := NewTypeConverter()
87
88 // Convert string to int
89 result, _ := converter.Convert("42", "int")
90 fmt.Printf("String '42' as int: %v (type: %T)\n", result, result)
91
92 // Convert float to int
93 result, _ = converter.Convert(3.14, "int")
94 fmt.Printf("Float 3.14 as int: %v (type: %T)\n", result, result)
95
96 // Convert to string
97 result, _ = converter.Convert(42, "string")
98 fmt.Printf("Int 42 as string: %v (type: %T)\n", result, result)
99
100 // Convert to bool
101 result, _ = converter.Convert("true", "bool")
102 fmt.Printf("String 'true' as bool: %v (type: %T)\n", result, result)
103}
104// run
Reflection-Based Configuration Parsing
Building configuration parsers with reflection allows you to automatically map configuration data to structs without boilerplate:
1package main
2
3import (
4 "fmt"
5 "reflect"
6 "strconv"
7 "strings"
8)
9
10// ConfigParser parses flat configuration into nested structures
11type ConfigParser struct {
12 separator string
13}
14
15// NewConfigParser creates a new configuration parser
16func NewConfigParser(sep string) *ConfigParser {
17 return &ConfigParser{separator: sep}
18}
19
20// Parse maps flat config to a struct using reflection
21func (cp *ConfigParser) Parse(config map[string]string, target interface{}) error {
22 val := reflect.ValueOf(target)
23 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
24 return fmt.Errorf("target must be pointer to struct")
25 }
26
27 val = val.Elem()
28 typ := val.Type()
29
30 for i := 0; i < val.NumField(); i++ {
31 field := val.Field(i)
32 fieldType := typ.Field(i)
33
34 if !field.CanSet() {
35 continue
36 }
37
38 // Get config key from tag
39 tag := fieldType.Tag.Get("config")
40 if tag == "" {
41 tag = strings.ToLower(fieldType.Name)
42 }
43
44 if value, exists := config[tag]; exists {
45 if err := cp.setFieldValue(field, value); err != nil {
46 return fmt.Errorf("failed to set %s: %w", fieldType.Name, err)
47 }
48 }
49 }
50
51 return nil
52}
53
54// setFieldValue sets a field value based on its type
55func (cp *ConfigParser) setFieldValue(field reflect.Value, value string) error {
56 switch field.Kind() {
57 case reflect.String:
58 field.SetString(value)
59 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
60 i, err := strconv.ParseInt(value, 10, 64)
61 if err != nil {
62 return err
63 }
64 field.SetInt(i)
65 case reflect.Float32, reflect.Float64:
66 f, err := strconv.ParseFloat(value, 64)
67 if err != nil {
68 return err
69 }
70 field.SetFloat(f)
71 case reflect.Bool:
72 b, err := strconv.ParseBool(value)
73 if err != nil {
74 return err
75 }
76 field.SetBool(b)
77 default:
78 return fmt.Errorf("unsupported type: %v", field.Kind())
79 }
80 return nil
81}
82
83// Example config struct
84type DatabaseConfig struct {
85 Host string `config:"db_host"`
86 Port int `config:"db_port"`
87 MaxConns int `config:"db_max_conns"`
88 Timeout float64 `config:"db_timeout"`
89 Debug bool `config:"debug"`
90}
91
92func main() {
93 config := map[string]string{
94 "db_host": "localhost",
95 "db_port": "5432",
96 "db_max_conns": "100",
97 "db_timeout": "30.5",
98 "debug": "true",
99 }
100
101 var dbConfig DatabaseConfig
102 parser := NewConfigParser(":")
103
104 if err := parser.Parse(config, &dbConfig); err != nil {
105 fmt.Println("Parse error:", err)
106 } else {
107 fmt.Printf("Config: %+v\n", dbConfig)
108 }
109}
110// run
Reflection-Based Deep Copy
Creating deep copies of complex nested structures is simplified with reflection:
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8// DeepCopy creates a recursive copy of any value
9func DeepCopy(src interface{}) (interface{}, error) {
10 srcVal := reflect.ValueOf(src)
11 return deepCopyValue(srcVal), nil
12}
13
14func deepCopyValue(val reflect.Value) reflect.Value {
15 switch val.Kind() {
16 case reflect.Struct:
17 copy := reflect.New(val.Type()).Elem()
18 for i := 0; i < val.NumField(); i++ {
19 if val.Field(i).CanInterface() {
20 copy.Field(i).Set(deepCopyValue(val.Field(i)))
21 }
22 }
23 return copy
24
25 case reflect.Slice:
26 copy := reflect.MakeSlice(val.Type(), val.Len(), val.Cap())
27 reflect.Copy(copy, val)
28 return copy
29
30 case reflect.Map:
31 copy := reflect.MakeMap(val.Type())
32 for _, key := range val.MapKeys() {
33 copy.SetMapIndex(key, deepCopyValue(val.MapIndex(key)))
34 }
35 return copy
36
37 case reflect.Ptr:
38 if val.IsNil() {
39 return val
40 }
41 copy := reflect.New(val.Elem().Type())
42 copy.Elem().Set(deepCopyValue(val.Elem()))
43 return copy
44
45 default:
46 return val
47 }
48}
49
50// Example types
51type Address struct {
52 Street string
53 City string
54}
55
56type Person struct {
57 Name string
58 Age int
59 Address Address
60}
61
62func main() {
63 original := Person{
64 Name: "Alice",
65 Age: 30,
66 Address: Address{
67 Street: "123 Main St",
68 City: "Boston",
69 },
70 }
71
72 copied, _ := DeepCopy(original)
73 copiedPerson := copied.(Person)
74
75 // Modify copy
76 copiedPerson.Name = "Bob"
77 copiedPerson.Address.City = "NYC"
78
79 fmt.Printf("Original: %+v\n", original)
80 fmt.Printf("Copied: %+v\n", copiedPerson)
81}
82// run
Integration and Mastery - Reflection Best Practices
Key Takeaways for Production Reflection Code:
- Performance: Cache reflection results when possible (Type, FieldByName, NumField)
- Error Handling: Always check return values and handle panics in reflection code
- Type Safety: Validate types before accessing fields or calling methods
- Visibility: Remember that reflection only works with exported (capital) fields
- Composition: Use reflection for libraries, not application business logic
- Testing: Test reflection code thoroughly with various input types
Practice Exercises
Exercise 1: Struct to Map Converter
Difficulty: Intermediate | Time: 20-25 minutes
Learning Objectives:
- Master struct field inspection using reflection
- Understand type conversion between structs and maps
- Practice handling exported/unexported field visibility
Real-World Context:
Struct-to-map conversion is crucial in many real-world applications. It's used in JSON serialization APIs, configuration systems that need to transform data structures, database ORMs that map between structs and database records, and logging systems that need to serialize complex objects. Understanding this pattern will help you build flexible systems that can work with data in multiple formats while maintaining type safety.
Task:
Convert any struct to a map[string]interface{} using reflection, handling both exported and unexported fields appropriately, with support for nested structs and proper type conversion.
Solution
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func StructToMap(s interface{}) map[string]interface{} {
9 result := make(map[string]interface{})
10
11 v := reflect.ValueOf(s)
12 t := reflect.TypeOf(s)
13
14 // Handle pointer
15 if v.Kind() == reflect.Ptr {
16 v = v.Elem()
17 t = t.Elem()
18 }
19
20 if v.Kind() != reflect.Struct {
21 return result
22 }
23
24 for i := 0; i < v.NumField(); i++ {
25 field := v.Field(i)
26 fieldType := t.Field(i)
27
28 // Skip unexported fields
29 if !field.CanInterface() {
30 continue
31 }
32
33 result[fieldType.Name] = field.Interface()
34 }
35
36 return result
37}
38
39func main() {
40 type Person struct {
41 Name string
42 Age int
43 Email string
44 }
45
46 p := Person{
47 Name: "Alice",
48 Age: 30,
49 Email: "alice@example.com",
50 }
51
52 m := StructToMap(p)
53 fmt.Println(m)
54 // map[Age:30 Email:alice@example.com Name:Alice]
55}
Exercise 2: Generic Copy Function
Difficulty: Advanced | Time: 30-35 minutes
Learning Objectives:
- Master deep copying with recursive reflection
- Understand value vs pointer semantics in Go
- Practice handling circular references and complex data structures
Real-World Context:
Deep copy functions are essential in many applications. They're used in caching systems to create independent copies, in configuration management to avoid shared state, in testing frameworks to create test data, and in undo/redo systems to maintain state snapshots. Understanding how to implement generic deep copying will help you build robust systems that can safely duplicate complex data structures without unintended side effects.
Task:
Create a deep copy function that works with any type, handling structs, slices, maps, pointers, and potential circular references while maintaining type safety and performance.
Solution
1package main
2
3import (
4 "fmt"
5 "reflect"
6)
7
8func DeepCopy(src interface{}) interface{} {
9 if src == nil {
10 return nil
11 }
12
13 srcVal := reflect.ValueOf(src)
14 return deepCopyValue(srcVal).Interface()
15}
16
17func deepCopyValue(src reflect.Value) reflect.Value {
18 switch src.Kind() {
19 case reflect.Ptr:
20 if src.IsNil() {
21 return src
22 }
23 dst := reflect.New(src.Elem().Type())
24 dst.Elem().Set(deepCopyValue(src.Elem()))
25 return dst
26
27 case reflect.Struct:
28 dst := reflect.New(src.Type()).Elem()
29 for i := 0; i < src.NumField(); i++ {
30 if dst.Field(i).CanSet() {
31 dst.Field(i).Set(deepCopyValue(src.Field(i)))
32 }
33 }
34 return dst
35
36 case reflect.Slice:
37 if src.IsNil() {
38 return src
39 }
40 dst := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
41 for i := 0; i < src.Len(); i++ {
42 dst.Index(i).Set(deepCopyValue(src.Index(i)))
43 }
44 return dst
45
46 case reflect.Map:
47 if src.IsNil() {
48 return src
49 }
50 dst := reflect.MakeMap(src.Type())
51 for _, key := range src.MapKeys() {
52 dst.SetMapIndex(key, deepCopyValue(src.MapIndex(key)))
53 }
54 return dst
55
56 default:
57 return src
58 }
59}
60
61func main() {
62 type Person struct {
63 Name string
64 Age int
65 Friends []string
66 }
67
68 original := Person{
69 Name: "Alice",
70 Age: 30,
71 Friends: []string{"Bob", "Charlie"},
72 }
73
74 copied := DeepCopy(original).(Person)
75
76 // Modify copy
77 copied.Name = "Bob"
78 copied.Friends[0] = "Diana"
79
80 fmt.Printf("Original: %+v\n", original)
81 fmt.Printf("Copied: %+v\n", copied)
82}
Exercise 3: Struct Validator
Difficulty: Advanced | Time: 35-40 minutes
Learning Objectives:
- Master struct tag parsing and validation logic
- Understand custom validation rule implementation
- Practice building extensible validation frameworks
Real-World Context:
Struct validation is critical in web applications and API systems. It's used in HTTP request validation for user input, configuration file validation for system settings, database model validation before persistence, and form validation in web interfaces. Understanding how to build a validation framework will help you create robust, maintainable systems that can ensure data integrity across your entire application.
Task:
Create a validator that checks struct fields based on custom validation tags, supporting common validation rules like required, min/max length, email format, and custom validation functions with detailed error reporting.
Solution
1package main
2
3import (
4 "fmt"
5 "reflect"
6 "strings"
7)
8
9type ValidationError struct {
10 Field string
11 Message string
12}
13
14func Validate(s interface{}) []ValidationError {
15 var errors []ValidationError
16
17 v := reflect.ValueOf(s)
18 t := reflect.TypeOf(s)
19
20 if v.Kind() == reflect.Ptr {
21 v = v.Elem()
22 t = t.Elem()
23 }
24
25 if v.Kind() != reflect.Struct {
26 return errors
27 }
28
29 for i := 0; i < v.NumField(); i++ {
30 field := v.Field(i)
31 fieldType := t.Field(i)
32
33 tag := fieldType.Tag.Get("validate")
34 if tag == "" {
35 continue
36 }
37
38 rules := strings.Split(tag, ",")
39 for _, rule := range rules {
40 if err := validateRule(fieldType.Name, field, rule); err != nil {
41 errors = append(errors, *err)
42 }
43 }
44 }
45
46 return errors
47}
48
49func validateRule(fieldName string, value reflect.Value, rule string) *ValidationError {
50 parts := strings.Split(rule, "=")
51 ruleName := parts[0]
52
53 switch ruleName {
54 case "required":
55 if value.IsZero() {
56 return &ValidationError{
57 Field: fieldName,
58 Message: "field is required",
59 }
60 }
61
62 case "min":
63 if len(parts) < 2 {
64 return nil
65 }
66 minLen := 0
67 fmt.Sscanf(parts[1], "%d", &minLen)
68
69 if value.Kind() == reflect.String && value.Len() < minLen {
70 return &ValidationError{
71 Field: fieldName,
72 Message: fmt.Sprintf("must be at least %d characters", minLen),
73 }
74 }
75
76 case "max":
77 if len(parts) < 2 {
78 return nil
79 }
80 maxLen := 0
81 fmt.Sscanf(parts[1], "%d", &maxLen)
82
83 if value.Kind() == reflect.String && value.Len() > maxLen {
84 return &ValidationError{
85 Field: fieldName,
86 Message: fmt.Sprintf("must be at most %d characters", maxLen),
87 }
88 }
89 }
90
91 return nil
92}
93
94func main() {
95 type User struct {
96 Username string `validate:"required,min=3,max=20"`
97 Email string `validate:"required"`
98 Age int `validate:"required"`
99 }
100
101 user1 := User{Username: "ab", Email: "test@example.com", Age: 25}
102 errors := Validate(user1)
103
104 if len(errors) > 0 {
105 fmt.Println("Validation errors:")
106 for _, err := range errors {
107 fmt.Printf(" %s: %s\n", err.Field, err.Message)
108 }
109 } else {
110 fmt.Println("Validation passed")
111 }
112
113 user2 := User{Username: "alice", Email: "alice@example.com", Age: 30}
114 errors = Validate(user2)
115
116 if len(errors) > 0 {
117 fmt.Println("Validation errors:")
118 for _, err := range errors {
119 fmt.Printf(" %s: %s\n", err.Field, err.Message)
120 }
121 } else {
122 fmt.Println("Validation passed")
123 }
124}
Exercise 4: JSON-like Serializer
Difficulty: Expert | Time: 40-45 minutes
Learning Objectives:
- Master recursive serialization algorithms
- Understand custom format design and implementation
- Practice handling complex data structures and edge cases
Real-World Context:
Custom serializers are vital in many applications. They're used in API protocols that need optimized formats, in logging systems that require compact representation, in configuration file formats, and in inter-service communication protocols. Understanding how to build serializers will help you create efficient, domain-specific data formats that can improve performance and reduce bandwidth usage in distributed systems.
Task:
Build a simple serializer that converts structs to a custom format, handling nested structures, different data types, arrays, and objects with proper escaping and formatting while maintaining performance and readability.
Solution
1package main
2
3import (
4 "fmt"
5 "reflect"
6 "strings"
7)
8
9func Serialize(v interface{}) string {
10 val := reflect.ValueOf(v)
11 return serializeValue(val)
12}
13
14func serializeValue(v reflect.Value) string {
15 switch v.Kind() {
16 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
17 return fmt.Sprintf("%d", v.Int())
18
19 case reflect.Float32, reflect.Float64:
20 return fmt.Sprintf("%f", v.Float())
21
22 case reflect.String:
23 return fmt.Sprintf("\"%s\"", v.String())
24
25 case reflect.Bool:
26 return fmt.Sprintf("%t", v.Bool())
27
28 case reflect.Struct:
29 var parts []string
30 t := v.Type()
31
32 for i := 0; i < v.NumField(); i++ {
33 field := v.Field(i)
34 fieldType := t.Field(i)
35
36 if !field.CanInterface() {
37 continue
38 }
39
40 key := fieldType.Name
41 value := serializeValue(field)
42 parts = append(parts, fmt.Sprintf("%s:%s", key, value))
43 }
44
45 return "{" + strings.Join(parts, ",") + "}"
46
47 case reflect.Slice, reflect.Array:
48 var parts []string
49 for i := 0; i < v.Len(); i++ {
50 parts = append(parts, serializeValue(v.Index(i)))
51 }
52 return "[" + strings.Join(parts, ",") + "]"
53
54 case reflect.Map:
55 var parts []string
56 for _, key := range v.MapKeys() {
57 keyStr := serializeValue(key)
58 valStr := serializeValue(v.MapIndex(key))
59 parts = append(parts, fmt.Sprintf("%s:%s", keyStr, valStr))
60 }
61 return "{" + strings.Join(parts, ",") + "}"
62
63 default:
64 return "null"
65 }
66}
67
68func main() {
69 type Address struct {
70 City string
71 Country string
72 }
73
74 type Person struct {
75 Name string
76 Age int
77 Active bool
78 Address Address
79 Tags []string
80 }
81
82 p := Person{
83 Name: "Alice",
84 Age: 30,
85 Active: true,
86 Address: Address{
87 City: "New York",
88 Country: "USA",
89 },
90 Tags: []string{"developer", "gopher"},
91 }
92
93 result := Serialize(p)
94 fmt.Println(result)
95}
Exercise 5: Dependency Injection Container
Difficulty: Expert | Time: 45-50 minutes
Learning Objectives:
- Master advanced reflection for dependency management
- Understand dependency injection patterns and principles
- Practice building IoC containers
Real-World Context:
Dependency injection containers are fundamental in modern software architecture. They're used in enterprise applications for managing service lifecycles, in web frameworks for automatic dependency resolution, in microservices for loose coupling, and in testing frameworks for mock injection. Understanding how to build a DI container will help you create maintainable, testable, and loosely coupled applications that follow SOLID principles.
Task:
Build a simple dependency injection container that can register types, resolve dependencies, and automatically inject them into struct fields based on tags, handling circular dependencies, lifetime management, and error cases.
Solution
1package main
2
3import (
4 "errors"
5 "fmt"
6 "reflect"
7 "sync"
8)
9
10// Container manages dependency injection
11type Container struct {
12 services map[reflect.Type]interface{}
13 mu sync.RWMutex
14}
15
16func NewContainer() *Container {
17 return &Container{
18 services: make(map[reflect.Type]interface{}),
19 }
20}
21
22// Register registers a service by type
23func Register(service interface{}) error {
24 c.mu.Lock()
25 defer c.mu.Unlock()
26
27 t := reflect.TypeOf(service)
28 if t.Kind() == reflect.Ptr {
29 t = t.Elem()
30 }
31
32 c.services[t] = service
33 return nil
34}
35
36// RegisterAs registers a service with a specific type
37func RegisterAs(serviceType reflect.Type, service interface{}) error {
38 c.mu.Lock()
39 defer c.mu.Unlock()
40
41 c.services[serviceType] = service
42 return nil
43}
44
45// Resolve gets a service by type
46func Resolve(serviceType reflect.Type) {
47 c.mu.RLock()
48 defer c.mu.RUnlock()
49
50 service, exists := c.services[serviceType]
51 if !exists {
52 return nil, fmt.Errorf("service not found for type: %v", serviceType)
53 }
54
55 return service, nil
56}
57
58// Inject injects dependencies into struct fields with `inject:""` tag
59func Inject(target interface{}) error {
60 v := reflect.ValueOf(target)
61
62 // Must be a pointer to a struct
63 if v.Kind() != reflect.Ptr {
64 return errors.New("target must be a pointer")
65 }
66
67 v = v.Elem()
68 if v.Kind() != reflect.Struct {
69 return errors.New("target must be a pointer to struct")
70 }
71
72 t := v.Type()
73
74 for i := 0; i < v.NumField(); i++ {
75 field := v.Field(i)
76 fieldType := t.Field(i)
77
78 // Check for inject tag
79 if tag := fieldType.Tag.Get("inject"); tag != "" {
80 if !field.CanSet() {
81 return fmt.Errorf("cannot set field %s", fieldType.Name)
82 }
83
84 // Resolve the dependency
85 service, err := c.Resolve(field.Type())
86 if err != nil {
87 if tag == "optional" {
88 // Optional dependency - skip if not found
89 continue
90 }
91 return fmt.Errorf("failed to inject %s: %w", fieldType.Name, err)
92 }
93
94 // Set the field
95 field.Set(reflect.ValueOf(service))
96 }
97 }
98
99 return nil
100}
101
102// Build creates and injects dependencies into a new instance
103func Build(targetType reflect.Type) {
104 // Create new instance
105 v := reflect.New(targetType)
106
107 // Inject dependencies
108 if err := c.Inject(v.Interface()); err != nil {
109 return nil, err
110 }
111
112 return v.Interface(), nil
113}
114
115// Example interfaces and implementations
116type Logger interface {
117 Log(message string)
118}
119
120type ConsoleLogger struct {
121 prefix string
122}
123
124func Log(message string) {
125 fmt.Printf("[%s] %s\n", l.prefix, message)
126}
127
128type Database interface {
129 Query(sql string) string
130}
131
132type MockDatabase struct {
133 name string
134}
135
136func Query(sql string) string {
137 return fmt.Sprintf("MockDB[%s]: %s", db.name, sql)
138}
139
140// Service that depends on Logger and Database
141type UserService struct {
142 Logger Logger `inject:""`
143 DB Database `inject:""`
144 Optional *string `inject:"optional"` // Optional dependency
145}
146
147func GetUser(id int) {
148 s.Logger.Log(fmt.Sprintf("Getting user %d", id))
149 result := s.DB.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %d", id))
150 s.Logger.Log(result)
151}
152
153// Service with no dependencies
154type EmailService struct {
155 SMTPHost string
156}
157
158func Send(to, subject string) {
159 fmt.Printf("Sending email to %s: %s\n", to, subject, s.SMTPHost)
160}
161
162// Service that depends on other services
163type NotificationService struct {
164 Logger Logger `inject:""`
165 Email *EmailService `inject:""`
166}
167
168func Notify(user string, message string) {
169 s.Logger.Log(fmt.Sprintf("Notifying %s", user))
170 s.Email.Send(user, message)
171}
172
173func main() {
174 // Create container
175 container := NewContainer()
176
177 // Register services
178 logger := &ConsoleLogger{prefix: "APP"}
179 container.RegisterAs(reflect.TypeOf((*Logger)(nil)).Elem(), logger)
180
181 db := &MockDatabase{name: "testdb"}
182 container.RegisterAs(reflect.TypeOf((*Database)(nil)).Elem(), db)
183
184 emailService := &EmailService{SMTPHost: "smtp.example.com"}
185 container.Register(emailService)
186
187 // Build UserService with automatic dependency injection
188 fmt.Println("=== Building UserService ===")
189 userService := &UserService{}
190 if err := container.Inject(userService); err != nil {
191 fmt.Printf("Failed to inject: %v\n", err)
192 return
193 }
194
195 userService.GetUser(123)
196
197 // Build NotificationService
198 fmt.Println("\n=== Building NotificationService ===")
199 notifService := &NotificationService{}
200 if err := container.Inject(notifService); err != nil {
201 fmt.Printf("Failed to inject: %v\n", err)
202 return
203 }
204
205 notifService.Notify("user@example.com", "Welcome!")
206
207 // Use Build method to create and inject in one step
208 fmt.Println("\n=== Using Build method ===")
209 service, err := container.Build(reflect.TypeOf(UserService{}))
210 if err != nil {
211 fmt.Printf("Failed to build: %v\n", err)
212 return
213 }
214
215 userSvc := service.(*UserService)
216 userSvc.GetUser(456)
217
218 // Demonstrate optional dependencies
219 fmt.Println("\n=== Optional dependency ===")
220 us := &UserService{}
221 if err := container.Inject(us); err != nil {
222 fmt.Printf("Error: %v\n", err)
223 } else {
224 fmt.Println("Injection succeeded")
225 us.GetUser(789)
226 }
227}
Output:
=== Building UserService ===
[APP] Getting user 123
[APP] MockDB[testdb]: SELECT * FROM users WHERE id = 123
=== Building NotificationService ===
[APP] Notifying user@example.com
Sending email to user@example.com: Welcome!
=== Using Build method ===
[APP] Getting user 456
[APP] MockDB[testdb]: SELECT * FROM users WHERE id = 456
=== Optional dependency ===
Injection succeeded
[APP] Getting user 789
[APP] MockDB[testdb]: SELECT * FROM users WHERE id = 789
Key Features:
- Register services by type or interface
- Automatic dependency resolution using reflection
- Field injection based on
inject:""struct tags - Optional dependencies with
inject:"optional" - Thread-safe with mutex locking
- Build method creates and injects in one step
- Works with interfaces for loose coupling
Summary - Mastering Go Reflection
Reflection is Go's answer to runtime type inspection and manipulation. While it's more complex than compile-time generics, it unlocks capabilities that are impossible with static typing alone. Reflection powers some of Go's most important libraries: encoding/json, GORM, testing frameworks, and configuration systems.
Core Reflection Functions
reflect.TypeOf()- Get type information at runtimereflect.ValueOf()- Get value information at runtime- Use
.Elem()- Dereference pointers to get underlying values - Check
.CanSet()- Verify writability before modifying values Field.Tag.Get()- Access struct field tags for metadata.Call()- Invoke functions dynamically at runtimereflect.New()- Create new instances of types at runtimereflect.DeepEqual()- Compare complex nested types
When to Use Reflection
Use reflection when:
- You need to work with types determined at runtime
- You're building libraries that must handle unknown types (JSON, database, validation)
- You need to implement dynamic dispatch or plugin systems
- You're building configuration systems with struct mapping
- You need to introspect code for analysis or documentation
Avoid reflection when:
- You know all types at compile time (use generics instead)
- Performance is critical in hot paths
- You can solve the problem with interfaces
- The added complexity isn't justified by the benefit
Production Best Practices
- Cache Results: Reflection operations are expensive. Cache
reflect.Type, field lookups, and method information. - Validate Types: Always check types before calling methods or accessing fields. Use
Kind()andType()to verify expectations. - Error Handling: Reflection operations can panic. Wrap them with error handling and provide clear error messages.
- Documentation: Struct tags are crucial. Document what tags your reflection code expects and supports.
- Performance: Reflection is 10-100x slower than direct access. Use it only where necessary and cache where possible.
- Test Thoroughly: Test reflection code with various types, including edge cases like nil values and zero values.
Reflection vs Generics
- Generics: Use for known types with performance requirements. Compile-time type safety.
- Reflection: Use for unknown types, dynamic behavior, and metaprogramming capabilities.
The best Go code uses both: generics for reusable libraries, reflection for deep introspection and dynamic systems.
The Reflection Philosophy
Reflection embodies Go's practical philosophy: when you need runtime flexibility, Go gives you powerful tools with honest performance trade-offs. Reflection makes the cost explicit (it's slow), which encourages you to use it thoughtfully.
Master reflection for powerful metaprogramming, but use it wisely! Your elegant, performant code depends on choosing the right tool for each job.