Advanced Reflection Techniques

With a solid understanding of reflection fundamentals, we can now explore more sophisticated techniques that enable powerful metaprogramming capabilities in Go. These advanced patterns are what power many of Go’s most flexible libraries and frameworks.

Dynamic Method Invocation

One of reflection’s most powerful capabilities is the ability to call methods dynamically at runtime:

package main

import (
	"fmt"
	"reflect"
)

type Calculator struct {
	result float64
}

func (c *Calculator) Add(x float64) {
	c.result += x
}

func (c *Calculator) Subtract(x float64) {
	c.result -= x
}

func (c *Calculator) Multiply(x float64) {
	c.result *= x
}

func (c *Calculator) Divide(x float64) {
	c.result /= x
}

func (c *Calculator) Result() float64 {
	return c.result
}

// ExecuteOperation dynamically calls a method on the calculator
func ExecuteOperation(calc *Calculator, operation string, value float64) error {
	// Get the reflect.Value of the calculator pointer
	v := reflect.ValueOf(calc)
	
	// Find the method by name
	method := v.MethodByName(operation)
	if !method.IsValid() {
		return fmt.Errorf("method not found: %s", operation)
	}
	
	// Check if the method takes one float64 argument
	methodType := method.Type()
	if methodType.NumIn() != 1 || methodType.In(0).Kind() != reflect.Float64 {
		return fmt.Errorf("method %s does not take a single float64 argument", operation)
	}
	
	// Call the method with the value
	args := []reflect.Value{reflect.ValueOf(value)}
	method.Call(args)
	return nil
}

func main() {
	calc := &Calculator{result: 10}
	
	// Execute a sequence of operations dynamically
	operations := []struct {
		name  string
		value float64
	}{
		{"Add", 5},      // 10 + 5 = 15
		{"Multiply", 2}, // 15 * 2 = 30
		{"Subtract", 8}, // 30 - 8 = 22
		{"Divide", 2},   // 22 / 2 = 11
	}
	
	for _, op := range operations {
		if err := ExecuteOperation(calc, op.name, op.value); err != nil {
			fmt.Printf("Error: %v\n", err)
			continue
		}
		fmt.Printf("After %s(%.1f): %.1f\n", op.name, op.value, calc.Result())
	}
}

Output:

After Add(5.0): 15.0
After Multiply(2.0): 30.0
After Subtract(8.0): 22.0
After Divide(2.0): 11.0

This pattern enables plugin systems, command dispatchers, and other architectures where the exact methods to call aren’t known until runtime.

Deep Struct Copying and Cloning

Reflection enables deep copying of complex nested structures:

package main

import (
	"fmt"
	"reflect"
)

// DeepCopy creates a deep copy of any value
func DeepCopy(v interface{}) interface{} {
	// Handle nil
	if v == nil {
		return nil
	}
	
	// Get reflect.Value of the input
	original := reflect.ValueOf(v)
	
	// Handle simple types that can be copied directly
	switch original.Kind() {
	case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64, reflect.String:
		return original.Interface()
	}
	
	// Create a new value of the same type as the original
	copy := reflect.New(original.Type()).Elem()
	
	// Handle different complex types
	switch original.Kind() {
	case reflect.Ptr:
		// If it's nil, return nil
		if original.IsNil() {
			return nil
		}
		
		// Create a deep copy of what the pointer points to
		originalValue := original.Elem().Interface()
		copyValue := DeepCopy(originalValue)
		
		// Create a new pointer to the copied value
		copyPtr := reflect.New(reflect.TypeOf(copyValue))
		copyPtr.Elem().Set(reflect.ValueOf(copyValue))
		return copyPtr.Interface()
		
	case reflect.Struct:
		// Copy each field
		for i := 0; i < original.NumField(); i++ {
			field := original.Field(i)
			if field.CanInterface() { // Skip unexported fields
				fieldCopy := DeepCopy(field.Interface())
				copy.Field(i).Set(reflect.ValueOf(fieldCopy))
			}
		}
		return copy.Interface()
		
	case reflect.Slice:
		// Handle nil slice
		if original.IsNil() {
			return reflect.Zero(original.Type()).Interface()
		}
		
		// Create a new slice with the same length and capacity
		copySlice := reflect.MakeSlice(original.Type(), original.Len(), original.Cap())
		
		// Copy each element
		for i := 0; i < original.Len(); i++ {
			elem := original.Index(i).Interface()
			copyElem := DeepCopy(elem)
			copySlice.Index(i).Set(reflect.ValueOf(copyElem))
		}
		return copySlice.Interface()
		
	case reflect.Map:
		// Handle nil map
		if original.IsNil() {
			return reflect.Zero(original.Type()).Interface()
		}
		
		// Create a new map
		copyMap := reflect.MakeMap(original.Type())
		
		// Copy each key-value pair
		for _, key := range original.MapKeys() {
			originalValue := original.MapIndex(key).Interface()
			copyKey := DeepCopy(key.Interface())
			copyValue := DeepCopy(originalValue)
			copyMap.SetMapIndex(reflect.ValueOf(copyKey), reflect.ValueOf(copyValue))
		}
		return copyMap.Interface()
		
	default:
		// For other types, return as is (may not be a deep copy)
		return original.Interface()
	}
}

type Address struct {
	Street string
	City   string
}

type Person struct {
	Name    string
	Age     int
	Address *Address
	Roles   []string
	Data    map[string]interface{}
}

func main() {
	// Create a complex nested structure
	original := Person{
		Name: "Alice",
		Age:  30,
		Address: &Address{
			Street: "123 Main St",
			City:   "Wonderland",
		},
		Roles: []string{"Admin", "User"},
		Data: map[string]interface{}{
			"id":      12345,
			"active":  true,
			"profile": &Address{Street: "Work Address", City: "Office City"},
		},
	}
	
	// Create a deep copy
	copy := DeepCopy(original).(Person)
	
	// Modify the original to demonstrate the copy is independent
	original.Name = "Modified Alice"
	original.Address.Street = "456 Changed St"
	original.Roles[0] = "Modified Admin"
	original.Data["active"] = false
	
	// Print both to compare
	fmt.Println("Original:")
	fmt.Printf("  Name: %s\n", original.Name)
	fmt.Printf("  Address: %s, %s\n", original.Address.Street, original.Address.City)
	fmt.Printf("  Roles: %v\n", original.Roles)
	fmt.Printf("  Active: %v\n", original.Data["active"])
	
	fmt.Println("\nCopy:")
	fmt.Printf("  Name: %s\n", copy.Name)
	fmt.Printf("  Address: %s, %s\n", copy.Address.Street, copy.Address.City)
	fmt.Printf("  Roles: %v\n", copy.Roles)
	fmt.Printf("  Active: %v\n", copy.Data["active"])
}

Output:

Original:
  Name: Modified Alice
  Address: 456 Changed St, Wonderland
  Roles: [Modified Admin User]
  Active: false

Copy:
  Name: Alice
  Address: 123 Main St, Wonderland
  Roles: [Admin User]
  Active: true

This deep copying technique is valuable for creating independent copies of complex data structures, which is essential in concurrent applications or when working with immutable data patterns.

Advanced Struct Tag Processing

Struct tags are a powerful feature in Go that enable metadata-driven programming. Advanced reflection techniques can extract and process this metadata in sophisticated ways:

package main

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// Validator defines validation rules for struct fields
type Validator struct {
	rules map[string][]ValidationRule
}

// ValidationRule represents a single validation rule
type ValidationRule struct {
	Name  string
	Param string
}

// ValidationError represents a validation error
type ValidationError struct {
	Field string
	Rule  string
	Msg   string
}

// NewValidator creates a new validator
func NewValidator() *Validator {
	return &Validator{
		rules: make(map[string][]ValidationRule),
	}
}

// ParseValidationRules extracts validation rules from struct tags
func (v *Validator) ParseValidationRules(s interface{}) {
	t := reflect.TypeOf(s)
	
	// If pointer, get the underlying type
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	
	// Only process structs
	if t.Kind() != reflect.Struct {
		return
	}
	
	// Iterate through all fields
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		
		// Get the validate tag
		validateTag := field.Tag.Get("validate")
		if validateTag == "" {
			continue
		}
		
		// Parse the validation rules
		fieldRules := []ValidationRule{}
		rules := strings.Split(validateTag, ",")
		
		for _, rule := range rules {
			// Split rule into name and parameter
			parts := strings.SplitN(rule, "=", 2)
			name := parts[0]
			param := ""
			if len(parts) > 1 {
				param = parts[1]
			}
			
			fieldRules = append(fieldRules, ValidationRule{
				Name:  name,
				Param: param,
			})
		}
		
		v.rules[field.Name] = fieldRules
	}
}

// Validate validates a struct against the parsed rules
func (v *Validator) Validate(s interface{}) []ValidationError {
	errors := []ValidationError{}
	val := reflect.ValueOf(s)
	
	// If pointer, get the underlying value
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	
	// Only process structs
	if val.Kind() != reflect.Struct {
		return errors
	}
	
	// Validate each field
	for fieldName, rules := range v.rules {
		field := val.FieldByName(fieldName)
		
		// Skip if field doesn't exist
		if !field.IsValid() {
			continue
		}
		
		// Apply each rule
		for _, rule := range rules {
			var err *ValidationError
			
			switch rule.Name {
			case "required":
				if isZero(field) {
					err = &ValidationError{
						Field: fieldName,
						Rule:  "required",
						Msg:   fmt.Sprintf("%s is required", fieldName),
					}
				}
				
			case "min":
				min, _ := strconv.Atoi(rule.Param)
				switch field.Kind() {
				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
					if field.Int() < int64(min) {
						err = &ValidationError{
							Field: fieldName,
							Rule:  "min",
							Msg:   fmt.Sprintf("%s must be at least %d", fieldName, min),
						}
					}
				case reflect.String:
					if field.Len() < min {
						err = &ValidationError{
							Field: fieldName,
							Rule:  "min",
							Msg:   fmt.Sprintf("%s must be at least %d characters", fieldName, min),
						}
					}
				}
				
			case "max":
				max, _ := strconv.Atoi(rule.Param)
				switch field.Kind() {
				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
					if field.Int() > int64(max) {
						err = &ValidationError{
							Field: fieldName,
							Rule:  "max",
							Msg:   fmt.Sprintf("%s must be at most %d", fieldName, max),
						}
					}
				case reflect.String:
					if field.Len() > max {
						err = &ValidationError{
							Field: fieldName,
							Rule:  "max",
							Msg:   fmt.Sprintf("%s must be at most %d characters", fieldName, max),
						}
					}
				}
			}
			
			if err != nil {
				errors = append(errors, *err)
			}
		}
	}
	
	return errors
}

// isZero checks if a value is the zero value for its type
func isZero(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.String:
		return v.Len() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Ptr, reflect.Interface:
		return v.IsNil()
	}
	return false
}

type User struct {
	Username string `validate:"required,min=3,max=20"`
	Password string `validate:"required,min=8"`
	Email    string `validate:"required"`
	Age      int    `validate:"min=18,max=120"`
}

func main() {
	// Create a validator and parse rules
	validator := NewValidator()
	validator.ParseValidationRules(User{})
	
	// Test with valid user
	validUser := User{
		Username: "johndoe",
		Password: "securepass123",
		Email:    "[email protected]",
		Age:      30,
	}
	
	errors := validator.Validate(validUser)
	fmt.Printf("Valid user has %d validation errors\n", len(errors))
	
	// Test with invalid user
	invalidUser := User{
		Username: "jo",
		Password: "weak",
		Email:    "",
		Age:      15,
	}
	
	errors = validator.Validate(invalidUser)
	fmt.Printf("Invalid user has %d validation errors:\n", len(errors))
	for _, err := range errors {
		fmt.Printf("  - %s\n", err.Msg)
	}
}

Output:

Valid user has 0 validation errors
Invalid user has 4 validation errors:
  - Username must be at least 3 characters
  - Password must be at least 8 characters
  - Email is required
  - Age must be at least 18

This validation framework demonstrates how reflection can be used to build flexible, metadata-driven systems. Similar approaches power many popular Go libraries like encoding/json, gorm, and validation frameworks.

Dynamic Struct Creation

Reflection allows for creating new struct types at runtime, which is particularly useful for data mapping scenarios:

package main

import (
	"fmt"
	"reflect"
)

// DynamicStruct allows building struct types at runtime
type DynamicStruct struct {
	fields     []reflect.StructField
	fieldNames map[string]bool
}

// NewDynamicStruct creates a new dynamic struct builder
func NewDynamicStruct() *DynamicStruct {
	return &DynamicStruct{
		fields:     make([]reflect.StructField, 0),
		fieldNames: make(map[string]bool),
	}
}

// AddField adds a field to the dynamic struct
func (ds *DynamicStruct) AddField(name string, typ reflect.Type, tag string) error {
	if ds.fieldNames[name] {
		return fmt.Errorf("field %s already exists", name)
	}
	
	field := reflect.StructField{
		Name: name,
		Type: typ,
		Tag:  reflect.StructTag(tag),
	}
	
	ds.fields = append(ds.fields, field)
	ds.fieldNames[name] = true
	return nil
}

// Build creates a new struct type from the defined fields
func (ds *DynamicStruct) Build() reflect.Type {
	return reflect.StructOf(ds.fields)
}

// New creates a new instance of the struct
func (ds *DynamicStruct) New() reflect.Value {
	return reflect.New(ds.Build()).Elem()
}

func main() {
	// Create a dynamic struct for database mapping
	dynamicStruct := NewDynamicStruct()
	
	// Add fields based on database schema
	dynamicStruct.AddField("ID", reflect.TypeOf(int64(0)), `json:"id" db:"id"`)
	dynamicStruct.AddField("Name", reflect.TypeOf(""), `json:"name" db:"name"`)
	dynamicStruct.AddField("Email", reflect.TypeOf(""), `json:"email" db:"email"`)
	dynamicStruct.AddField("CreatedAt", reflect.TypeOf(int64(0)), `json:"created_at" db:"created_at"`)
	
	// Create a new instance
	instance := dynamicStruct.New()
	
	// Set field values
	instance.FieldByName("ID").SetInt(1001)
	instance.FieldByName("Name").SetString("Dynamic User")
	instance.FieldByName("Email").SetString("[email protected]")
	instance.FieldByName("CreatedAt").SetInt(1628097357)
	
	// Print the struct type and values
	fmt.Printf("Struct Type: %v\n", instance.Type())
	fmt.Printf("Field Names: %v\n", instance.Type().NumField())
	
	for i := 0; i < instance.NumField(); i++ {
		field := instance.Type().Field(i)
		value := instance.Field(i)
		fmt.Printf("%s (%s): %v [tags: %s]\n", field.Name, field.Type, value.Interface(), field.Tag)
	}
	
	// Convert to a map (e.g., for JSON serialization)
	valueMap := make(map[string]interface{})
	for i := 0; i < instance.NumField(); i++ {
		field := instance.Type().Field(i)
		value := instance.Field(i)
		valueMap[field.Name] = value.Interface()
	}
	
	fmt.Printf("\nAs Map: %v\n", valueMap)
}

Output:

Struct Type: struct { ID int64; Name string; Email string; CreatedAt int64 }
Field Names: 4
ID (int64): 1001 [tags: json:"id" db:"id"]
Name (string): Dynamic User [tags: json:"name" db:"name"]
Email (string): [email protected] [tags: json:"email" db:"email"]
CreatedAt (int64): 1628097357 [tags: json:"created_at" db:"created_at"]

As Map: map[CreatedAt:1628097357 Email:[email protected] ID:1001 Name:Dynamic User]

This technique is particularly useful for ORM libraries, data mappers, and scenarios where the exact structure of data isn’t known until runtime.

Custom JSON Marshaling with Reflection

Reflection enables building custom serialization systems that can handle complex requirements:

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
)

// CustomMarshaler provides custom JSON marshaling with advanced options
type CustomMarshaler struct {
	// FieldNameMapping defines how struct field names are transformed
	// e.g., "FieldName" -> "field_name"
	FieldNameMapping func(string) string
	
	// OmitEmpty controls whether empty fields are omitted
	OmitEmpty bool
	
	// IncludeFields specifies which fields to include (if empty, include all)
	IncludeFields map[string]bool
	
	// ExcludeFields specifies which fields to exclude
	ExcludeFields map[string]bool
}

// NewCustomMarshaler creates a new custom marshaler
func NewCustomMarshaler() *CustomMarshaler {
	return &CustomMarshaler{
		FieldNameMapping: func(s string) string { return s },
		OmitEmpty:        false,
		IncludeFields:    make(map[string]bool),
		ExcludeFields:    make(map[string]bool),
	}
}

// WithSnakeCase configures the marshaler to use snake_case field names
func (m *CustomMarshaler) WithSnakeCase() *CustomMarshaler {
	m.FieldNameMapping = func(s string) string {
		var result strings.Builder
		for i, r := range s {
			if i > 0 && 'A' <= r && r <= 'Z' {
				result.WriteByte('_')
			}
			result.WriteRune(r)
		}
		return strings.ToLower(result.String())
	}
	return m
}

// WithOmitEmpty configures the marshaler to omit empty fields
func (m *CustomMarshaler) WithOmitEmpty() *CustomMarshaler {
	m.OmitEmpty = true
	return m
}

// WithIncludeFields specifies which fields to include
func (m *CustomMarshaler) WithIncludeFields(fields ...string) *CustomMarshaler {
	for _, field := range fields {
		m.IncludeFields[field] = true
	}
	return m
}

// WithExcludeFields specifies which fields to exclude
func (m *CustomMarshaler) WithExcludeFields(fields ...string) *CustomMarshaler {
	for _, field := range fields {
		m.ExcludeFields[field] = true
	}
	return m
}

// Marshal converts a struct to a map using reflection
func (m *CustomMarshaler) Marshal(v interface{}) (map[string]interface{}, error) {
	result := make(map[string]interface{})
	
	val := reflect.ValueOf(v)
	if val.Kind() == reflect.Ptr {
		if val.IsNil() {
			return nil, fmt.Errorf("cannot marshal nil pointer")
		}
		val = val.Elem()
	}
	
	if val.Kind() != reflect.Struct {
		return nil, fmt.Errorf("cannot marshal non-struct type: %s", val.Type())
	}
	
	typ := val.Type()
	for i := 0; i < val.NumField(); i++ {
		field := typ.Field(i)
		fieldVal := val.Field(i)
		
		// Skip unexported fields
		if !field.IsExported() {
			continue
		}
		
		// Check include/exclude lists
		if len(m.IncludeFields) > 0 && !m.IncludeFields[field.Name] {
			continue
		}
		if m.ExcludeFields[field.Name] {
			continue
		}
		
		// Check if field is empty and should be omitted
		if m.OmitEmpty && isEmptyValue(fieldVal) {
			continue
		}
		
		// Get field name from tag or transform the struct field name
		jsonTag := field.Tag.Get("json")
		var fieldName string
		if jsonTag != "" && jsonTag != "-" {
			parts := strings.SplitN(jsonTag, ",", 2)
			fieldName = parts[0]
		} else {
			fieldName = m.FieldNameMapping(field.Name)
		}
		
		// Get the field value
		var fieldValue interface{}
		
		// Handle nested structs recursively
		if fieldVal.Kind() == reflect.Struct && field.Anonymous {
			// For embedded structs, merge fields into parent
			nestedMap, err := m.Marshal(fieldVal.Interface())
			if err != nil {
				return nil, err
			}
			for k, v := range nestedMap {
				result[k] = v
			}
			continue
		} else if fieldVal.Kind() == reflect.Struct {
			// For regular struct fields, create a nested map
			nestedMap, err := m.Marshal(fieldVal.Interface())
			if err != nil {
				return nil, err
			}
			fieldValue = nestedMap
		} else {
			// For other types, use the value directly
			fieldValue = fieldVal.Interface()
		}
		
		result[fieldName] = fieldValue
	}
	
	return result, nil
}

// MarshalJSON implements the json.Marshaler interface
func (m *CustomMarshaler) MarshalJSON(v interface{}) ([]byte, error) {
	mapped, err := m.Marshal(v)
	if err != nil {
		return nil, err
	}
	return json.Marshal(mapped)
}

// isEmptyValue checks if a value is empty (zero value or empty collection)
func isEmptyValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
		return v.Len() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Interface, reflect.Ptr:
		return v.IsNil()
	}
	return false
}

// Example types for demonstration
type Address struct {
	Street     string `json:"street"`
	City       string `json:"city"`
	PostalCode string `json:"postal_code"`
}

type Contact struct {
	Email string `json:"email"`
	Phone string `json:"phone"`
}

type Person struct {
	ID        int     `json:"id"`
	FirstName string  `json:"first_name"`
	LastName  string  `json:"last_name"`
	Age       int     `json:"age"`
	Address   Address `json:"address"`
	Contact   Contact `json:"contact"`
	Notes     string  `json:"notes,omitempty"`
	Internal  string  `json:"-"` // Excluded by JSON tag
	private   string  // Unexported field
}

func main() {
	person := Person{
		ID:        1001,
		FirstName: "John",
		LastName:  "Doe",
		Age:       30,
		Address: Address{
			Street:     "123 Main St",
			City:       "Anytown",
			PostalCode: "12345",
		},
		Contact: Contact{
			Email: "[email protected]",
			Phone: "",
		},
		Notes:    "",
		Internal: "Internal data",
		private:  "Private data",
	}
	
	// Standard JSON marshaling
	standardJSON, _ := json.MarshalIndent(person, "", "  ")
	fmt.Println("Standard JSON:")
	fmt.Println(string(standardJSON))
	
	// Custom marshaling with snake_case and omit empty
	customMarshaler := NewCustomMarshaler().
		WithSnakeCase().
		WithOmitEmpty().
		WithExcludeFields("Age")
	
	customJSON, _ := customMarshaler.MarshalJSON(person)
	fmt.Println("\nCustom JSON (snake_case, omit empty, exclude Age):")
	fmt.Println(string(customJSON))
	
	// Custom marshaling with only specific fields
	limitedMarshaler := NewCustomMarshaler().
		WithIncludeFields("FirstName", "LastName", "Contact")
	
	limitedJSON, _ := limitedMarshaler.MarshalJSON(person)
	fmt.Println("\nLimited JSON (only FirstName, LastName, Contact):")
	fmt.Println(string(limitedJSON))
}

Output:

Standard JSON:
{
  "id": 1001,
  "first_name": "John",
  "last_name": "Doe",
  "age": 30,
  "address": {
    "street": "123 Main St",
    "city": "Anytown",
    "postal_code": "12345"
  },
  "contact": {
    "email": "[email protected]",
    "phone": ""
  }
}

Custom JSON (snake_case, omit empty, exclude Age):
{"address":{"city":"anytown","postal_code":"12345","street":"123 main st"},"contact":{"email":"[email protected]"},"first_name":"john","id":1001,"last_name":"doe"}

Limited JSON (only FirstName, LastName, Contact):
{"Contact":{"email":"[email protected]","phone":""},"FirstName":"John","LastName":"Doe"}

This custom marshaling system demonstrates how reflection can be used to build flexible serialization systems that go beyond what’s available in the standard library.

These advanced reflection techniques showcase the power of Go’s reflection system for building flexible, dynamic systems. However, it’s important to remember that reflection comes with performance costs and reduced type safety. In the next section, we’ll explore code generation as an alternative approach that moves complexity from runtime to build time.