TTuck

The reflect package lets a program inspect types and values at run time. It powers encoding/json, fmt, and text/template. Most code never needs it directly, but it explains how those packages read struct tags and fields.

package main

import (
	"fmt"
	"reflect"
	"strings"
)

A struct with tags, like the ones encoding/json reads.

type User struct {
	Name  string `json:"name" validate:"required"`
	Email string `json:"email"`
	Age   int    `json:"age,omitempty"`
	admin bool
}

func (u User) Greeting() string { return "hi " + u.Name }

validate checks every field tagged validate:"required" in any struct.

func validate(v any) []string {
	var missing []string
	rv := reflect.ValueOf(v)
	rt := rv.Type()
	for i := range rt.NumField() {
		f := rt.Field(i)
		if f.Tag.Get("validate") == "required" && rv.Field(i).IsZero() {
			missing = append(missing, f.Name)
		}
	}
	return missing
}

func main() {
	u := User{Name: "Ada", Email: "[email protected]"}

TypeOf returns the type, and Kind its basic category.

	t := reflect.TypeOf(u)
	fmt.Println(t, t.Kind(), t.NumField())

Walk the fields, reading names, types, and tags.

	for i := range t.NumField() {
		f := t.Field(i)
		fmt.Printf("%-6s %-7s json=%q exported=%t\n", f.Name, f.Type, f.Tag.Get("json"), f.IsExported())
	}

ValueOf wraps a value. Read fields by name.

	v := reflect.ValueOf(u)
	fmt.Println(v.FieldByName("Email"))

To change a value through reflection, start from a pointer and call Elem.

	pv := reflect.ValueOf(&u).Elem()
	pv.FieldByName("Age").SetInt(36)
	fmt.Println(u.Age, pv.FieldByName("Age").CanSet(), pv.FieldByName("admin").CanSet())

Methods can be looked up and called too.

	m := v.MethodByName("Greeting")
	fmt.Println(m.Call(nil)[0])

A generic validator built on tags.

	fmt.Println("missing:", validate(User{Email: "[email protected]"}))
	fmt.Println("missing:", validate(u))

reflect.DeepEqual compares values deeply, including slices and maps.

	a := map[string][]int{"x": {1, 2}}
	b := map[string][]int{"x": {1, 2}}
	fmt.Println(reflect.DeepEqual(a, b))

Kind lets code handle whole families of types at once.

	for _, x := range []any{1, "s", []int{}, map[int]int{}, &u, strings.ToUpper} {
		fmt.Print(reflect.TypeOf(x).Kind(), " ")
	}
	fmt.Println()
}
$ go run ./91_reflection
main.User struct 4
Name   string  json="name" exported=true
Email  string  json="email" exported=true
Age    int     json="age,omitempty" exported=true
admin  bool    json="" exported=false
[email protected]
36 true false
hi Ada
missing: [Name]
missing: []
true
int string slice map ptr func