Struct Validation with Go Playground Validator
Validates a user payload with built-in tags and a custom phone validator to produce readable error lists.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
package main
import (
"fmt"
"regexp"
"github.com/go-playground/validator/v10"
)
// User represents a user model with validation tags
type User struct {
Username string `json:"username" validate:"required,alphanum,min=3,max=20"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"required,gte=18,lte=120"`
Phone string `json:"phone" validate:"required,phoneNumber"` // Custom validation
Password string `json:"password" validate:"required,min=8,max=72,containsany=!@#$%^&*"`
}
// Custom validation function for phone numbers
func validatePhoneNumber(fl validator.FieldLevel) bool {
phoneRegex := regexp.MustCompile(`^\+?[0-9]{10,15}$`)
return phoneRegex.MatchString(fl.Field().String())
}
// validateStruct performs validation on a struct and returns detailed errors
func validateStruct(s interface{}) map[string]string {
// Initialize validator
validate := validator.New()
// Register custom validation
if err := validate.RegisterValidation("phoneNumber", validatePhoneNumber); err != nil {
return map[string]string{"system": fmt.Sprintf("failed to register custom validator: %v", err)}
}
// Perform validation
errs := validate.Struct(s)
if errs == nil {
return nil
}
// Process validation errors into a user-friendly map
errorMap := make(map[string]string)
for _, err := range errs.(validator.ValidationErrors) {
field := err.Field()
switch err.Tag() {
case "required":
errorMap[field] = fmt.Sprintf("%s is a required field", field)
case "email":
errorMap[field] = "Invalid email format"
case "alphanum":
errorMap[field] = "Must contain only alphanumeric characters"
case "min":
errorMap[field] = fmt.Sprintf("Must be at least %s characters/value", err.Param())
case "max":
errorMap[field] = fmt.Sprintf("Must not exceed %s characters/value", err.Param())
case "gte":
errorMap[field] = fmt.Sprintf("Must be greater than or equal to %s", err.Param())
case "lte":
errorMap[field] = fmt.Sprintf("Must be less than or equal to %s", err.Param())
case "containsany":
errorMap[field] = fmt.Sprintf("Must contain at least one special character (%s)", err.Param())
case "phoneNumber":
errorMap[field] = "Invalid phone number format (expected +1234567890 or 1234567890)"
default:
errorMap[field] = fmt.Sprintf("Validation failed for rule: %s", err.Tag())
}
}
return errorMap
}
func main() {
// Example 1: Invalid user data
invalidUser := User{
Username: "jo!e", // Contains special character
Email: "joe@example", // Invalid email
Age: 17, // Below minimum age
Phone: "12345", // Invalid phone number
Password: "password", // No special characters
}
fmt.Println("Validation errors for invalid user:")
errors := validateStruct(invalidUser)
for field, msg := range errors {
fmt.Printf(" %s: %s\n", field, msg)
}
// Example 2: Valid user data
validUser := User{
Username: "joe123",
Email: "[email protected]",
Age: 25,
Phone: "+12345678901",
Password: "Passw0rd!",
}
fmt.Println("\nValidation for valid user:")
if errors := validateStruct(validUser); errors == nil {
fmt.Println(" No validation errors - user data is valid!")
} else {
for field, msg := range errors {
fmt.Printf(" %s: %s\n", field, msg)
}
}
}How It Works
Validates a user payload with built-in tags and a custom phone validator to produce readable error lists.
Registers a custom phoneNumber rule, sets validation tags on User fields, runs validator.Struct, and collects validation errors into user-friendly strings for each field.
Key Concepts
- 1Uses go-playground/validator tags for required, email, length, and containsany rules.
- 2Custom phone validator enforces E.164-style numbers via a precompiled regex.
- 3Errors are aggregated and formatted with field-specific messages.
When to Use This Pattern
- Validating API request bodies before processing.
- Checking configuration or CLI input before execution.
- Ensuring user registration data meets policy requirements.
Best Practices
- Reuse a single validator instance to keep custom rules registered.
- Return clear validation messages rather than generic 400 errors.
- Keep regex and custom logic fast to avoid slowing request handling.
Go Version1.16
Difficultyintermediate
Production ReadyYes
Lines of Code105