Reflection Toolkit

Exercise: Reflection Toolkit

Difficulty - Intermediate

Learning Objectives

  • Understand the reflect package fundamentals
  • Learn to inspect types and values at runtime
  • Practice struct tag parsing
  • Master dynamic function calls
  • Implement generic utilities using reflection

Problem Statement

Create a reflector package that implements common reflection-based utilities.

Function Signatures

 1package reflector
 2
 3// StructToMap converts a struct to a map[string]interface{}
 4func StructToMap(obj interface{})
 5
 6// GetFieldValue retrieves a struct field value by name
 7func GetFieldValue(obj interface{}, fieldName string)
 8
 9// SetFieldValue sets a struct field value by name
10func SetFieldValue(obj interface{}, fieldName string, value interface{}) error
11
12// GetStructTags returns all tags for struct fields
13func GetStructTags(obj interface{}, tagName string)
14
15// CallMethod calls a method by name with given arguments
16func CallMethod(obj interface{}, methodName string, args ...interface{})
17
18// DeepCopy creates a deep copy of any value
19func DeepCopy(src interface{})
20
21// CompareStructs compares two structs field by field
22func CompareStructs(a, b interface{})

Solution

Click to see the complete solution
  1package reflector
  2
  3import (
  4	"fmt"
  5	"reflect"
  6)
  7
  8// StructToMap converts a struct to map
  9func StructToMap(obj interface{}) {
 10	result := make(map[string]interface{})
 11	v := reflect.ValueOf(obj)
 12
 13	if v.Kind() == reflect.Ptr {
 14		v = v.Elem()
 15	}
 16
 17	if v.Kind() != reflect.Struct {
 18		return nil, fmt.Errorf("expected struct, got %v", v.Kind())
 19	}
 20
 21	t := v.Type()
 22	for i := 0; i < v.NumField(); i++ {
 23		field := t.Field(i)
 24		if field.PkgPath != "" { // Skip unexported fields
 25			continue
 26		}
 27		result[field.Name] = v.Field(i).Interface()
 28	}
 29
 30	return result, nil
 31}
 32
 33// GetFieldValue retrieves field value by name
 34func GetFieldValue(obj interface{}, fieldName string) {
 35	v := reflect.ValueOf(obj)
 36	if v.Kind() == reflect.Ptr {
 37		v = v.Elem()
 38	}
 39
 40	if v.Kind() != reflect.Struct {
 41		return nil, fmt.Errorf("expected struct, got %v", v.Kind())
 42	}
 43
 44	field := v.FieldByName(fieldName)
 45	if !field.IsValid() {
 46		return nil, fmt.Errorf("field %s not found", fieldName)
 47	}
 48
 49	return field.Interface(), nil
 50}
 51
 52// SetFieldValue sets field value by name
 53func SetFieldValue(obj interface{}, fieldName string, value interface{}) error {
 54	v := reflect.ValueOf(obj)
 55	if v.Kind() != reflect.Ptr {
 56		return fmt.Errorf("expected pointer to struct")
 57	}
 58
 59	v = v.Elem()
 60	if v.Kind() != reflect.Struct {
 61		return fmt.Errorf("expected struct, got %v", v.Kind())
 62	}
 63
 64	field := v.FieldByName(fieldName)
 65	if !field.IsValid() {
 66		return fmt.Errorf("field %s not found", fieldName)
 67	}
 68
 69	if !field.CanSet() {
 70		return fmt.Errorf("field %s cannot be set", fieldName)
 71	}
 72
 73	val := reflect.ValueOf(value)
 74	if field.Type() != val.Type() {
 75		return fmt.Errorf("type mismatch: expected %v, got %v", field.Type(), val.Type())
 76	}
 77
 78	field.Set(val)
 79	return nil
 80}
 81
 82// GetStructTags returns all tags for struct fields
 83func GetStructTags(obj interface{}, tagName string) {
 84	result := make(map[string]string)
 85	t := reflect.TypeOf(obj)
 86
 87	if t.Kind() == reflect.Ptr {
 88		t = t.Elem()
 89	}
 90
 91	if t.Kind() != reflect.Struct {
 92		return nil, fmt.Errorf("expected struct, got %v", t.Kind())
 93	}
 94
 95	for i := 0; i < t.NumField(); i++ {
 96		field := t.Field(i)
 97		tag := field.Tag.Get(tagName)
 98		if tag != "" {
 99			result[field.Name] = tag
100		}
101	}
102
103	return result, nil
104}
105
106// CallMethod calls a method by name
107func CallMethod(obj interface{}, methodName string, args ...interface{}) {
108	v := reflect.ValueOf(obj)
109	method := v.MethodByName(methodName)
110
111	if !method.IsValid() {
112		return nil, fmt.Errorf("method %s not found", methodName)
113	}
114
115	// Convert args to reflect.Value slice
116	in := make([]reflect.Value, len(args))
117	for i, arg := range args {
118		in[i] = reflect.ValueOf(arg)
119	}
120
121	// Call method
122	results := method.Call(in)
123
124	// Convert results back to interface{}
125	out := make([]interface{}, len(results))
126	for i, result := range results {
127		out[i] = result.Interface()
128	}
129
130	return out, nil
131}
132
133// DeepCopy creates a deep copy
134func DeepCopy(src interface{}) {
135	v := reflect.ValueOf(src)
136	return deepCopyValue(v).Interface(), nil
137}
138
139func deepCopyValue(v reflect.Value) reflect.Value {
140	switch v.Kind() {
141	case reflect.Ptr:
142		if v.IsNil() {
143			return v
144		}
145		newPtr := reflect.New(v.Elem().Type())
146		newPtr.Elem().Set(deepCopyValue(v.Elem()))
147		return newPtr
148
149	case reflect.Struct:
150		newStruct := reflect.New(v.Type()).Elem()
151		for i := 0; i < v.NumField(); i++ {
152			if v.Field(i).CanInterface() {
153				newStruct.Field(i).Set(deepCopyValue(v.Field(i)))
154			}
155		}
156		return newStruct
157
158	case reflect.Slice:
159		newSlice := reflect.MakeSlice(v.Type(), v.Len(), v.Cap())
160		for i := 0; i < v.Len(); i++ {
161			newSlice.Index(i).Set(deepCopyValue(v.Index(i)))
162		}
163		return newSlice
164
165	case reflect.Map:
166		newMap := reflect.MakeMap(v.Type())
167		for _, key := range v.MapKeys() {
168			newMap.SetMapIndex(key, deepCopyValue(v.MapIndex(key)))
169		}
170		return newMap
171
172	default:
173		return v
174	}
175}
176
177// CompareStructs compares two structs
178func CompareStructs(a, b interface{}) {
179	va := reflect.ValueOf(a)
180	vb := reflect.ValueOf(b)
181
182	if va.Type() != vb.Type() {
183		return false, []string{"different types"}
184	}
185
186	if va.Kind() == reflect.Ptr {
187		va = va.Elem()
188		vb = vb.Elem()
189	}
190
191	if va.Kind() != reflect.Struct {
192		return false, []string{"not structs"}
193	}
194
195	var diffs []string
196	for i := 0; i < va.NumField(); i++ {
197		fieldA := va.Field(i)
198		fieldB := vb.Field(i)
199		fieldName := va.Type().Field(i).Name
200
201		if !reflect.DeepEqual(fieldA.Interface(), fieldB.Interface()) {
202			diffs = append(diffs, fmt.Sprintf("%s: %v != %v",
203				fieldName, fieldA.Interface(), fieldB.Interface()))
204		}
205	}
206
207	return len(diffs) == 0, diffs
208}

Key Takeaways

  1. Reflection is Powerful: Can inspect and manipulate types at runtime
  2. Performance Cost: Reflection is slower than direct access
  3. Type Safety: Lose compile-time type checking
  4. Use Sparingly: Only when absolutely necessary
  5. Tag Parsing: Powerful for serialization and validation