go 59 lines · 7 steps

Struct validation with reflection in Go

A tiny library that reads `validate` struct tags and checks each field against named rules using reflection.

Explained by highlit
1package validate
2 
3import (
4 "fmt"
5 "reflect"
6 "strconv"
7 "strings"
8)
9 
10type Error struct {
11 Field string
12 Rule string
13}
14 
15func (e Error) Error() string {
16 return fmt.Sprintf("field %q failed rule %q", e.Field, e.Rule)
17}
18 
19func Struct(v interface{}) []error {
20 val := reflect.ValueOf(v)
21 if val.Kind() == reflect.Ptr {
22 val = val.Elem()
23 }
24 typ := val.Type()
25 
26 var errs []error
27 for i := 0; i < typ.NumField(); i++ {
28 field := typ.Field(i)
29 tag := field.Tag.Get("validate")
30 if tag == "" || tag == "-" {
31 continue
32 }
33 fv := val.Field(i)
34 for _, rule := range strings.Split(tag, ",") {
35 name, arg, _ := strings.Cut(rule, "=")
36 if !check(fv, name, arg) {
37 errs = append(errs, Error{Field: field.Name, Rule: rule})
38 }
39 }
40 }
41 return errs
42}
43 
44func check(fv reflect.Value, name, arg string) bool {
45 switch name {
46 case "required":
47 return !fv.IsZero()
48 case "min":
49 n, _ := strconv.Atoi(arg)
50 return fv.Kind() == reflect.String && len(fv.String()) >= n
51 case "max":
52 n, _ := strconv.Atoi(arg)
53 return fv.Kind() == reflect.String && len(fv.String()) <= n
54 case "email":
55 return strings.Contains(fv.String(), "@")
56 default:
57 return true
58 }
59}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Struct tags let you attach declarative metadata that code reads at runtime via reflection.
  2. 2Collecting errors into a slice reports every failure at once instead of stopping at the first.
  3. 3A rule dispatcher keyed on a name string makes the validator easy to extend with new checks.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Struct validation with reflection in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code