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
go
func (h *ExportHandler) StreamExport(c *gin.Context) { datasetID := c.Param("id") ctx := c.Request.Context()
Streaming NDJSON progress with Gin
streaming
goroutines
channels
Advanced
8 steps
go
package handler type flightResult struct { status int
Deduping in-flight requests in Gin
middleware
concurrency
deduplication
Advanced
9 steps
go
type CreateUserInput struct { Name string `json:"name" binding:"required,min=2,max=64"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=8"`
Turning Gin validation errors into JSON
validation
request-binding
error-handling
Intermediate
9 steps
javascript
const express = require('express'); const { createProxyMiddleware, fixRequestBody } = require('http-proxy-middleware'); const router = express.Router();
Building an API gateway proxy in Express
reverse-proxy
middleware
api-gateway
Intermediate
6 steps
rust
use axum::{ extract::{FromRequestParts, Query}, http::{request::Parts, StatusCode}, };
Building a custom Axum extractor for query filters
extractors
query-parsing
enums
Intermediate
8 steps
go
package middleware import ( "net/http"
Per-IP write rate limiting in Gin
rate-limiting
middleware
concurrency
Intermediate
8 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.