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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Struct tags let you attach declarative metadata that code reads at runtime via reflection.
- 2Collecting errors into a slice reports every failure at once instead of stopping at the first.
- 3A rule dispatcher keyed on a name string makes the validator easy to extend with new checks.
Related explainers
python
import time from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path
Parallel file downloads with a thread pool
concurrency
thread-pool
streaming-io
Intermediate
8 steps
ruby
class ParamCoercer TYPES = { integer: ->(v) { Integer(v) }, float: ->(v) { Float(v) },
Coercing request params by schema in Ruby
lambdas
type-coercion
lookup-table
Intermediate
7 steps
python
from datetime import datetime from zoneinfo import ZoneInfo
Timezone-safe datetime handling in Python
timezones
datetime
normalization
Intermediate
4 steps
rust
use std::env; use std::time::Duration; #[derive(Debug, Clone)]
Loading typed config from environment variables in Rust
configuration
error-handling
generics
Intermediate
7 steps
java
public class EventBus { private final Map<Class<?>, List<Subscriber>> subscribers = new ConcurrentHashMap<>(); private final Executor executor;
Building an annotation-driven EventBus in Java
publish-subscribe
reflection
annotations
Intermediate
7 steps
go
package middleware import ( "bytes"
Verifying webhook HMAC signatures in Gin
hmac
middleware
webhooks
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/struct-validation-with-reflection-in-go-explained-go-1973/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.