Reflection in Practice

Updated

July 30, 2026

Overview

The reflect package provides runtime type inspection. Use sparingly—prefer generics or interfaces when possible.

Type and Value

import "reflect"

x := 42
t := reflect.TypeOf(x)   // Type information
v := reflect.ValueOf(x)  // Value information

fmt.Println(t.Name())    // int
fmt.Println(t.Kind())    // int
fmt.Println(v.Int())     // 42

Inspecting Structs

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

u := User{Name: "Alice", Email: "a@b.com"}
t := reflect.TypeOf(u)
v := reflect.ValueOf(u)

for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    value := v.Field(i)
    tag := field.Tag.Get("json")
    fmt.Printf("%s: %v (tag: %s)\n", field.Name, value, tag)
}

Modifying Values

x := 42
v := reflect.ValueOf(&x).Elem()  // Need pointer for modification
v.SetInt(100)
fmt.Println(x)  // 100

Calling Methods

type Calculator struct{}
func (c Calculator) Add(a, b int) int { return a + b }

c := Calculator{}
v := reflect.ValueOf(c)
method := v.MethodByName("Add")
args := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)}
result := method.Call(args)
fmt.Println(result[0].Int())  // 5

When to Use

Appropriate: - Serialization/deserialization - ORM mapping - Dependency injection - Test utilities

Avoid: - Performance-critical paths - Simple type conversions - When generics suffice

Summary

Function Purpose
TypeOf() Get type info
ValueOf() Get value wrapper
Kind() Base type category
Field() Access struct field
MethodByName() Access method

Worked example

Copy exported fields from map[string]any into a struct via reflection.

Save as main.go. Then:

go mod init example
go run .
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string
    Age  int
}

func mapToStruct(m map[string]any, dest any) error {
    v := reflect.ValueOf(dest)
    if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct {
        return fmt.Errorf("dest must be *struct")
    }
    v = v.Elem()
    t := v.Type()
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        if !f.IsExported() {
            continue
        }
        raw, ok := m[f.Name]
        if !ok {
            continue
        }
        fv := v.Field(i)
        rv := reflect.ValueOf(raw)
        if !rv.Type().AssignableTo(fv.Type()) {
            return fmt.Errorf("field %s: cannot assign %T", f.Name, raw)
        }
        fv.Set(rv)
    }
    return nil
}

func main() {
    var u User
    err := mapToStruct(map[string]any{"Name": "Ada", "Age": 36}, &u)
    fmt.Println("user:", u, "err:", err)
}

Expected output:

user: {Ada 36} err: <nil>

More examples

Read struct tags (serializer-style).

package main

import (
    "fmt"
    "reflect"
)

type Row struct {
    ID    int    `json:"id"`
    Email string `json:"email,omitempty"`
    Skip  string `json:"-"`
}

func main() {
    t := reflect.TypeOf(Row{})
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        fmt.Printf("%s tag=%q\n", f.Name, f.Tag.Get("json"))
    }
}

Expected output:

ID tag="id"
Email tag="email,omitempty"
Skip tag="-"

Runnable example

Save as main.go. Then:

go mod init example
go run .
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    age   int    // unexported: visible to reflect only within this package
}

type Calculator struct{}

func (Calculator) Add(a, b int) int { return a + b }

func main() {
    u := User{Name: "Alice", Email: "a@b.com", age: 30}
    t := reflect.TypeOf(u)
    v := reflect.ValueOf(u)

    fmt.Println("type:", t.Name(), "kind:", t.Kind())
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        fmt.Printf("field %s tag=%q value=%v\n", f.Name, f.Tag.Get("json"), v.Field(i).Interface())
    }

    // Modify via pointer + Elem
    x := 42
    rv := reflect.ValueOf(&x).Elem()
    rv.SetInt(100)
    fmt.Println("modified x:", x)

    // Call method by name
    c := Calculator{}
    m := reflect.ValueOf(c).MethodByName("Add")
    out := m.Call([]reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)})
    fmt.Println("Add via reflect:", out[0].Int())
}

Expected output:

type: User kind: struct
field Name tag="name" value=Alice
field Email tag="email" value=a@b.com
field age tag="" value=30
modified x: 100
Add via reflect: 5

What to notice: ValueOf(x) is not settable—you need a pointer and Elem(). Tags power serializers (JSON, ORMs). Prefer generics/interfaces when you know the types at compile time; reflection costs CPU and clarity.

Try next: Build a tiny MapToStruct helper that fills exported fields from map[string]any. Compare it to a typed constructor.