Reflection in Go
Reflection in Go allows you to inspect and manipulate types at runtime. It’s provided by the reflect package and is useful for building generic libraries, serialization frameworks, and other metaprogramming tasks. However, reflection should be used sparingly as it can make code harder to understand and maintain.
Basic Reflection
The reflect package provides two main types: Type and Value.
package main
import (
"fmt"
"reflect"
)
func main() {
var x int = 42
// Get the reflect.Value
v := reflect.ValueOf(x)
fmt.Println("Value:", v.Int()) // 42
// Get the reflect.Type
t := reflect.TypeOf(x)
fmt.Println("Type:", t.String()) // int
fmt.Println("Kind:", t.Kind()) // int
}Inspecting Structs
Reflection is particularly useful for working with structs:
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func PrintStructInfo(s interface{}) {
v := reflect.ValueOf(s)
t := v.Type()
fmt.Printf("Struct: %s\n", t.Name())
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldType := t.Field(i)
fmt.Printf(" Field %d: %s %s = %v\n",
i, fieldType.Name, fieldType.Type, field.Interface())
// Check for tags
if tag := fieldType.Tag.Get("json"); tag != "" {
fmt.Printf(" JSON tag: %s\n", tag)
}
}
}
func main() {
p := Person{Name: "Alice", Age: 30}
PrintStructInfo(p)
}Modifying Values
To modify values through reflection, you need to pass a pointer:
package main
import (
"fmt"
"reflect"
)
func SetField(obj interface{}, fieldName string, value interface{}) error {
v := reflect.ValueOf(obj)
// Check if it's a pointer
if v.Kind() != reflect.Ptr {
return fmt.Errorf("obj must be a pointer")
}
// Get the value it points to
v = v.Elem()
// Find the field
field := v.FieldByName(fieldName)
if !field.IsValid() {
return fmt.Errorf("field %s not found", fieldName)
}
// Check if field is settable
if !field.CanSet() {
return fmt.Errorf("field %s cannot be set", fieldName)
}
// Set the value
val := reflect.ValueOf(value)
if field.Type() != val.Type() {
return fmt.Errorf("type mismatch: expected %s, got %s", field.Type(), val.Type())
}
field.Set(val)
return nil
}
func main() {
p := &Person{Name: "Bob", Age: 25}
err := SetField(p, "Name", "Charlie")
if err != nil {
fmt.Println("Error:", err)
}
err = SetField(p, "Age", 35)
if err != nil {
fmt.Println("Error:", err)
}
fmt.Printf("%+v\n", p) // &{Name:Charlie Age:35}
}Calling Functions
You can call functions dynamically using reflection:
package main
import (
"fmt"
"reflect"
)
func Add(a, b int) int {
return a + b
}
func Greet(name string) string {
return "Hello, " + name
}
func CallFunction(fn interface{}, args ...interface{}) interface{} {
v := reflect.ValueOf(fn)
// Prepare arguments
argValues := make([]reflect.Value, len(args))
for i, arg := range args {
argValues[i] = reflect.ValueOf(arg)
}
// Call the function
resultValues := v.Call(argValues)
// Return the first result (simplified)
if len(resultValues) > 0 {
return resultValues[0].Interface()
}
return nil
}
func main() {
result1 := CallFunction(Add, 3, 5)
fmt.Println("Add result:", result1) // 6
result2 := CallFunction(Greet, "World")
fmt.Println("Greet result:", result2) // Hello, World
}Creating Types Dynamically
Reflection can create new types at runtime:
package main
import (
"fmt"
"reflect"
)
func main() {
// Create a slice type dynamically
sliceType := reflect.SliceOf(reflect.TypeOf(""))
sliceValue := reflect.MakeSlice(sliceType, 0, 0)
// Add elements
elem1 := reflect.ValueOf("hello")
sliceValue = reflect.Append(sliceValue, elem1)
elem2 := reflect.ValueOf("world")
sliceValue = reflect.Append(sliceValue, elem2)
fmt.Println(sliceValue.Interface()) // [hello world]
// Create a map type
mapType := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
mapValue := reflect.MakeMap(mapType)
key1 := reflect.ValueOf("key1")
value1 := reflect.ValueOf(42)
mapValue.SetMapIndex(key1, value1)
fmt.Println(mapValue.Interface()) // map[key1:42]
}JSON Serialization with Reflection
A simple JSON encoder using reflection:
package main
import (
"fmt"
"reflect"
"strconv"
"strings"
)
func MarshalJSON(v interface{}) string {
val := reflect.ValueOf(v)
typ := val.Type()
if typ.Kind() == reflect.Struct {
var fields []string
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldType := typ.Field(i)
key := fieldType.Name
var value string
switch field.Kind() {
case reflect.String:
value = fmt.Sprintf("\"%s\"", field.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
value = strconv.FormatInt(field.Int(), 10)
case reflect.Bool:
value = strconv.FormatBool(field.Bool())
default:
value = "\"unknown\""
}
fields = append(fields, fmt.Sprintf("\"%s\":%s", key, value))
}
return "{" + strings.Join(fields, ",") + "}"
}
return "{}"
}
func main() {
p := Person{Name: "Alice", Age: 30}
json := MarshalJSON(p)
fmt.Println(json) // {"Name":"Alice","Age":30}
}When to Use Reflection
Reflection is powerful but should be used carefully:
Good uses:
- Building serialization libraries (JSON, XML)
- Dependency injection frameworks
- ORM libraries
- Testing utilities
Avoid when:
- You can solve the problem with interfaces
- Performance is critical
- Code clarity is important
Performance Considerations
Reflection is slower than direct code. Here’s a comparison:
package main
import (
"reflect"
"testing"
)
func DirectAccess(p Person) string {
return p.Name
}
func ReflectAccess(p Person) string {
v := reflect.ValueOf(p)
field := v.FieldByName("Name")
return field.String()
}
func BenchmarkDirect(b *testing.B) {
p := Person{Name: "Test"}
for i := 0; i < b.N; i++ {
_ = DirectAccess(p)
}
}
func BenchmarkReflect(b *testing.B) {
p := Person{Name: "Test"}
for i := 0; i < b.N; i++ {
_ = ReflectAccess(p)
}
}Direct access is much faster than reflection.
Best Practices
- Use reflection sparingly: Only when necessary
- Check for nil values: Always validate before using
- Handle errors gracefully: Reflection operations can panic
- Cache reflection results: If you need to do the same operations repeatedly
- Document reflective code: Explain what you’re doing and why
Reflection is a powerful tool in Go’s toolbox, but like any powerful tool, it should be used responsibly. When possible, prefer compile-time solutions over runtime reflection.
For more details, see the reflect package documentation.
If you need to work with JSON in Go, check out our JSON handling tutorial.