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
- Reflection is Powerful: Can inspect and manipulate types at runtime
- Performance Cost: Reflection is slower than direct access
- Type Safety: Lose compile-time type checking
- Use Sparingly: Only when absolutely necessary
- Tag Parsing: Powerful for serialization and validation
Related Topics
- Reflection - Main reflection tutorial
- Interfaces - Interface patterns
- Type System - Advanced types