Reflection in Go
By Syncfusion®, Inc. · Last updated
Go reflection with the reflect package: reflect.Type, reflect.Value and Kind, inspecting types and struct fields and tags, reading and setting values, and DeepEqual.
What Is Reflection?
Reflection = a program looking at its own values and types while it runs.
With reflection, code can ask a value:
- What type are you?
- What kind of thing are you: a number, a struct, a slice?
- If you are a struct, what fields do you have, and what are their tags?
- Can I change you?
Go's reflect package answers these questions.
Simple lesson: reflection lets a program inspect values whose type it did not know when it was written.
A Real-World Example
Think of the school's lost-and-found box.
- The office does not know in advance what will be dropped in.
- For each item, they look at it: Is it a bottle? A jacket? A book?
- For a bag, they open it and list what is inside.
- Then they write a label for it.
Now the mapping:
- The unknown item = a value of type
any. - Looking at it =
reflect.TypeOfandreflect.ValueOf. - Opening the bag = going through a struct's fields.
- The label = what your code prints or saves.
Why Reflection Is Used
encoding/jsonuses it to read your struct's fields and tags. See JSON.fmt.Printlnuses it to print any value.- Form and config libraries fill structs from text.
- Testing tools compare deep values with
reflect.DeepEqual.
Reflection is powerful, but use it rarely in your own code.
- It is slower than normal code.
- Mistakes show up as panics while running, not as build errors.
- Interfaces or generics are usually a better choice. See Generics.
What You Will Learn
In this lesson, you will learn:
- What reflection is and why it is used.
- The
reflectpackage:reflect.Typeandreflect.Value. - How to inspect types and struct fields.
- How to read values and work with dynamic data.
- How to change a value through reflection.
The reflect Package
reflect.Type
reflect.Type = the description of a type. Get it with reflect.TypeOf(x).
reflect.Value
reflect.Value = the value itself, wrapped so you can inspect it. Get it with reflect.ValueOf(x).
Type vs Kind
Kind = the general family of a type: int, string, struct, slice, map, ptr, and so on.
Pick a value and see its Type, Kind and Value.
- Type is exact:
map[string]int. - Kind is the family:
map. - Many different types share one kind. Every struct type has the kind
struct.
TypeOf, Kind and ValueOf
package main
import (
"fmt"
"reflect"
)
func main() {
values := []any{42, 3.5, "Asha", true, []int{91, 78}, map[string]int{"maths": 91}}
for _, v := range values {
t := reflect.TypeOf(v)
fmt.Println(t, "| kind:", t.Kind(), "| value:", reflect.ValueOf(v))
}
}
int | kind: int | value: 42
float64 | kind: float64 | value: 3.5
string | kind: string | value: Asha
bool | kind: bool | value: true
[]int | kind: slice | value: [91 78]
map[string]int | kind: map | value: map[maths:91]
Simple lesson: TypeOf tells you what something is, and ValueOf gives you the value to inspect.
Type Inspection
Inspecting Types
Use Kind() to decide what to do, like a type switch for families. See Interfaces and Polymorphism.
Describe any value by its kind
package main
import (
"fmt"
"reflect"
)
func describe(x any) string {
v := reflect.ValueOf(x)
switch v.Kind() {
case reflect.Int, reflect.Int64:
return fmt.Sprint("a whole number: ", v.Int())
case reflect.String:
return fmt.Sprint("text with ", v.Len(), " bytes")
case reflect.Slice:
return fmt.Sprint("a list of ", v.Len(), " items")
case reflect.Map:
return fmt.Sprint("a map with ", v.Len(), " keys")
default:
return "something else: " + v.Kind().String()
}
}
func main() {
fmt.Println(describe(7))
fmt.Println(describe("Green Valley"))
fmt.Println(describe([]string{"a", "b", "c"}))
fmt.Println(describe(map[int]bool{1: true}))
fmt.Println(describe(2.5))
}
a whole number: 7
text with 12 bytes
a list of 3 items
a map with 1 keys
something else: float64
v.Int(),v.Len()and similar methods only work for the right kinds.- Calling the wrong one panics. That is why we check
Kind()first.
Inspecting Struct Fields
Press Play to walk through a struct's fields.
List a struct's fields and tags
t.NumField()= how many fields.t.Field(i)= one field's details:Name,Type,Tag.- This is exactly how
encoding/jsonfinds your JSON names.
Read a custom tag for a form
package main
import (
"fmt"
"reflect"
)
type Admission struct {
StudentName string `label:"Student name" required:"yes"`
ParentPhone string `label:"Parent phone" required:"yes"`
Hobby string `label:"Hobby"`
}
func main() {
t := reflect.TypeOf(Admission{})
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
mark := ""
if f.Tag.Get("required") == "yes" {
mark = " *"
}
fmt.Println(f.Tag.Get("label") + mark)
}
}
Student name *
Parent phone *
Hobby
- One loop builds the labels for any form struct.
- Required fields get a
*.
In this lab, keep reflection to exported fields and built-in types.
The runner does not report the names of your own types (t.Name()), their methods, or unexported fields the way the real go tool does.
Simple lesson: Kind() tells you the family, and NumField/Field let you walk through a struct and its tags.
Value Inspection
Reading Values
Read struct field values
package main
import (
"fmt"
"reflect"
)
type Book struct {
Title string
Pages int
Lent bool
}
func main() {
b := Book{"Gitanjali", 103, true}
v := reflect.ValueOf(b)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
fmt.Printf("%s = %v\n", t.Field(i).Name, v.Field(i).Interface())
}
}
Title = Gitanjali
Pages = 103
Lent = true
v.Field(i)is the field's value, as areflect.Value..Interface()turns it back into a normalanyyou can print.
Changing Values
To change a value, reflect on a pointer, then use Elem().
Set a field by name
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Marks int
}
func main() {
s := Student{"Ravi", 70}
v := reflect.ValueOf(&s).Elem()
f := v.FieldByName("Marks")
fmt.Println(f.CanSet())
f.SetInt(85)
v.FieldByName("Name").SetString("Ravi Kumar")
fmt.Println(s)
}
true
{Ravi Kumar 85}
reflect.ValueOf(s)would hold a copy, so it cannot be set.reflect.ValueOf(&s).Elem()reaches the real struct.CanSet()tells you if a change is allowed.- The setter must match the kind:
SetIntfor ints,SetStringfor strings.
Working with Dynamic Data
Dynamic data = data whose shape is only known while the program runs.
Fill a struct from text pairs
package main
import (
"fmt"
"reflect"
"strconv"
)
type Settings struct {
School string
Periods int
Debug bool
}
func fill(target any, data map[string]string) {
v := reflect.ValueOf(target).Elem()
for key, text := range data {
f := v.FieldByName(key)
if !f.IsValid() || !f.CanSet() {
continue
}
switch f.Kind() {
case reflect.String:
f.SetString(text)
case reflect.Int:
n, _ := strconv.Atoi(text)
f.SetInt(int64(n))
case reflect.Bool:
f.SetBool(text == "true")
}
}
}
func main() {
var s Settings
fill(&s, map[string]string{"School": "GVS", "Periods": "8", "Debug": "true", "Colour": "blue"})
fmt.Printf("%+v\n", s)
}
{School:GVS Periods:8 Debug:true}
fillworks for any settings struct, without knowing its fields in advance.IsValid()is false for a missing field, likeColour, so it is skipped.- Config and form libraries work this way.
Deep Comparison
reflect.DeepEqual
package main
import (
"fmt"
"reflect"
)
func main() {
a := []int{91, 78, 85}
b := []int{91, 78, 85}
c := map[string][]int{"8A": {1, 2}}
d := map[string][]int{"8A": {1, 2}}
fmt.Println(reflect.DeepEqual(a, b))
fmt.Println(reflect.DeepEqual(c, d))
fmt.Println(reflect.DeepEqual(a, []int{91, 78}))
}
true
true
false
- Slices and maps cannot use
==. See Slices. reflect.DeepEqualcompares them element by element. Tests use it a lot.
Easy way to remember
- What is it? →
reflect.TypeOf(x),.Kind() - What is in it? →
reflect.ValueOf(x),.Field(i),.Len() - Change it? →
reflect.ValueOf(&x).Elem(), thenSetInt/SetString - Prefer interfaces or generics when you can.
Simple lesson: ValueOf reads values, ValueOf(&x).Elem() changes them, and DeepEqual compares deeply.
TL;DR
- Reflection = inspect types and values while running. Package
reflect. - reflect.Type =
reflect.TypeOf(x). reflect.Value =reflect.ValueOf(x). - Kind = the family:
int,string,slice,map,struct,ptr... - Struct fields =
NumField(),Field(i)→Name,Type,Tag.Get("json"). - Read =
v.Field(i).Interface(),v.Int(),v.Len(). - Change =
reflect.ValueOf(&x).Elem(), checkCanSet(), thenSetInt/SetString/SetBool. - DeepEqual = compare slices, maps and structs deeply.
- Use rarely: slower, and errors appear only while running.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
reflect.ValueOf(s).Field(0).SetInt(5) | Values can always be set | Reflect on &s, then Elem() | reflect: ... using unaddressable value panic. |
v.Int() on a string | Any getter works | Check Kind() first | call of reflect.Value.Int on string Value panic. |
| Setting an unexported field | Reflection can do anything | Only exported fields can be set | A panic. |
| Using reflection for simple jobs | It is more flexible | Interfaces or generics are clearer and faster | Slow, fragile code. |
== on two slices | Slices compare like arrays | Use reflect.DeepEqual | A build error. |
| Confusing Type and Kind | They are the same | Kind is the family, Type is exact | Wrong checks. |
Check Yourself
Exercises
What to Remember
- Reflection inspects types and values while the program runs.
TypeOfgives the type,Kind()the family,ValueOfthe value.- Walk struct fields with
NumFieldandField(i), and read tags withTag.Get. - To change a value, reflect on a pointer and use
Elem(). - Prefer interfaces and generics. Use reflection only when you must.
Next: Context. You will learn how to cancel work, set deadlines and time limits across goroutines.