Reflection

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/json and 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.Type and reflect.Value to 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/json and 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:

  1. Strict type system - Reflection respects Go's type safety
  2. Unexported fields - Private fields accessible for reading but not writing
  3. Interface{} foundation - reflect.ValueOf(any) works on any value
  4. Pointer semantics - Explicit .Elem() and .Addr() for pointer handling
  5. 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:

  1. πŸ” Type Inspection - Like looking at the blueprint of a house
  2. πŸ“¦ 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: Type is the specific type, Kind is 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:

  1. reflect.ValueOf(&x) creates a Value representing the pointer to x
  2. .Elem() dereferences the pointer to get the actual variable
  3. .CanSet() checks if the value is addressable and exported
  4. .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 x directly 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:

  1. Performance: Cache reflection results when possible (Type, FieldByName, NumField)
  2. Error Handling: Always check return values and handle panics in reflection code
  3. Type Safety: Validate types before accessing fields or calling methods
  4. Visibility: Remember that reflection only works with exported (capital) fields
  5. Composition: Use reflection for libraries, not application business logic
  6. 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 runtime
  • reflect.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 runtime
  • reflect.New() - Create new instances of types at runtime
  • reflect.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

  1. Cache Results: Reflection operations are expensive. Cache reflect.Type, field lookups, and method information.
  2. Validate Types: Always check types before calling methods or accessing fields. Use Kind() and Type() to verify expectations.
  3. Error Handling: Reflection operations can panic. Wrap them with error handling and provide clear error messages.
  4. Documentation: Struct tags are crucial. Document what tags your reflection code expects and supports.
  5. Performance: Reflection is 10-100x slower than direct access. Use it only where necessary and cache where possible.
  6. 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.