Skip to Content
Go Realm v1 is released 🎉

Part 1: Core Validations

Learn essential validation patterns for nullable fields, strings, numbers, booleans, and arrays.


Step 5: Nullable Fields & Optional Validation {#step5-nullable}

5.1 Understanding Nullable vs Required

In Go and databases, there’s a distinction between:

  • Required: Field must have a value
  • Optional: Field can be omitted
  • Nullable: Field can be explicitly null

5.2 Using Pointers for Nullable Fields

📝 Note about Example Files: All example files in examples/ have package main and func main(). Your IDE will show lint errors about “main redeclared” - this is normal and harmless. Each file is designed to run individually:

go run examples/nullable_demo.go go run examples/string_validations.go # etc...

📝 Create new file examples/nullable_demo.go:

package main import ( "fmt" "github.com/go-playground/validator/v10" ) func demoNullableFields() { validate := validator.New() fmt.Println("\n=== Nullable Fields Demo ===\n") type UserDTO struct { // Required field - must have value Name string `json:"name" validate:"required"` // Optional field - can be empty string // Only validates if not empty Bio string `json:"bio" validate:"omitempty,min=10,max=500"` // Nullable field - can be nil or have value // If present, must be valid email Email *string `json:"email" validate:"omitempty,email"` // Nullable number - if present, must be >= 18 Age *int `json:"age" validate:"omitempty,gte=18"` } // Test 1: All fields provided fmt.Println("Test 1: All fields provided") email1 := "john@example.com" age1 := 25 user1 := UserDTO{ Name: "John Doe", Bio: "Software developer with 5 years experience", Email: &email1, Age: &age1, } err := validate.Struct(user1) if err == nil { fmt.Println("✅ Validation passed") fmt.Printf(" Name: %s, Email: %s, Age: %d\n", user1.Name, *user1.Email, *user1.Age) } fmt.Println() // Test 2: Optional fields omitted (nil) fmt.Println("Test 2: Optional fields omitted") user2 := UserDTO{ Name: "Jane Smith", Bio: "", // Empty string - validation skipped Email: nil, // Nil pointer - validation skipped Age: nil, // Nil pointer - validation skipped } err = validate.Struct(user2) if err == nil { fmt.Println("✅ Validation passed (optional fields nil)") fmt.Printf(" Name: %s\n", user2.Name) } fmt.Println() // Test 3: Invalid optional values fmt.Println("Test 3: Invalid optional values") invalidEmail := "not-an-email" invalidAge := 15 user3 := UserDTO{ Name: "Bob Johnson", Bio: "Short", // Too short Email: &invalidEmail, // Present but invalid Age: &invalidAge, // Present but too young } err = validate.Struct(user3) if err != nil { fmt.Println("❌ Validation failed:") for _, err := range err.(validator.ValidationErrors) { fmt.Printf(" - %s: %s\n", err.Field(), err.Tag()) } } } func main() { demoNullableFields() }

Run:

go run examples/nullable_demo.go

✅ Expected Output:

=== Nullable Fields Demo === Test 1: All fields provided ✅ Validation passed Name: John Doe, Email: john@example.com, Age: 25 Test 2: Optional fields omitted ✅ Validation passed (optional fields nil) Name: Jane Smith Test 3: Invalid optional values ❌ Validation failed: - Bio: min - Email: email - Age: gte

5.3 Nullable Field Patterns

PatternGo TypeJSONValidationUse Case
Requiredstring"value"Always validatesMust always have value
Optional stringstring"" or "value"omitempty skips emptyOptional but validates if present
Nullable*stringnull or "value"omitempty skips nilCan be explicitly null
Pointer required*string"value"requiredPointer must not be nil

Key Rules:

  • Use omitempty for optional validation
  • Use pointers for truly nullable fields (JSON null)
  • Use required to enforce non-nil/non-zero values
  • Combine omitempty with other rules for “if present, must be valid”

Step 6: String Validations {#step6-strings}

6.1 Format Validations

String validation covers email, URLs, alpha characters, and more.

📝 Create examples/string_validations.go:

package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { validate := validator.New() fmt.Println("=== String Format Validations ===\n") type StringFormatDTO struct { // Email validation Email string `validate:"required,email"` // URL validation (must have scheme) Website string `validate:"omitempty,url"` // URI validation (can be relative path) APIPath string `validate:"omitempty,uri"` // Alpha only (letters A-Z, a-z) FirstName string `validate:"required,alpha"` // Alphanumeric only (letters + numbers) Username string `validate:"required,alphanum"` // Alpha with unicode support InternationalName string `validate:"omitempty,alphaunicode"` // ASCII only characters AsciiField string `validate:"omitempty,ascii"` // Lowercase only LowerTag string `validate:"omitempty,lowercase"` // Uppercase only UpperTag string `validate:"omitempty,uppercase"` // Hex color (#RGB or #RRGGBB) Color string `validate:"omitempty,hexcolor"` // UUID validation (v3, v4, v5) ID string `validate:"omitempty,uuid4"` // IP address (v4 or v6) IPAddr string `validate:"omitempty,ip"` // IPv4 specific IPv4Addr string `validate:"omitempty,ipv4"` // IPv6 specific IPv6Addr string `validate:"omitempty,ipv6"` // MAC address MACAddr string `validate:"omitempty,mac"` // JSON string JSONData string `validate:"omitempty,json"` // Base64 encoded Base64Data string `validate:"omitempty,base64"` // JWT token JWTToken string `validate:"omitempty,jwt"` } // Valid example fmt.Println("Test 1: Valid string formats") valid := StringFormatDTO{ Email: "user@example.com", Website: "https://example.com", APIPath: "/api/v1/users", FirstName: "John", Username: "john123", InternationalName: "José", AsciiField: "Hello123", LowerTag: "lowercase", UpperTag: "UPPERCASE", Color: "#FF5733", ID: "550e8400-e29b-41d4-a716-446655440000", IPAddr: "192.168.1.1", IPv4Addr: "10.0.0.1", IPv6Addr: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", MACAddr: "01:23:45:67:89:ab", JSONData: `{"key": "value"}`, Base64Data: "SGVsbG8gV29ybGQ=", JWTToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U", } err := validate.Struct(valid) if err == nil { fmt.Println("✅ All string format validations passed") fmt.Printf(" Email: %s\n", valid.Email) fmt.Printf(" Website: %s\n", valid.Website) fmt.Printf(" IP: %s\n", valid.IPAddr) } fmt.Println() // Invalid example fmt.Println("Test 2: Invalid string formats") invalid := StringFormatDTO{ Email: "not-an-email", Website: "not a url", APIPath: "", FirstName: "John123", // Contains numbers Username: "user name", // Contains space InternationalName: "Name!@#", // Special characters LowerTag: "NotLower", UpperTag: "notUpper", Color: "red", // Not hex format ID: "not-a-uuid", IPAddr: "999.999.999.999", IPv4Addr: "not-ipv4", IPv6Addr: "not-ipv6", MACAddr: "invalid-mac", JSONData: "not json", Base64Data: "not base64!!!", JWTToken: "not.a.jwt", } err = validate.Struct(invalid) if err != nil { fmt.Println("❌ Validation failed:") count := 0 for _, err := range err.(validator.ValidationErrors) { fmt.Printf(" - %s failed '%s'\n", err.Field(), err.Tag()) count++ if count >= 8 { fmt.Println(" - ... and more errors") break } } } fmt.Println() // String length validations fmt.Println("=== String Length Validations ===\n") type LengthDTO struct { // Minimum length Password string `validate:"required,min=8"` // Maximum length Username string `validate:"required,max=20"` // Exact length PIN string `validate:"required,len=4"` // Length range Bio string `validate:"required,min=10,max=500"` // Contains specific substring Description string `validate:"required,contains=Go"` // Excludes specific substring SafeInput string `validate:"required,excludes=<script>"` // Starts with BlogSlug string `validate:"required,startswith=blog-"` // Ends with ImageFile string `validate:"required,endswith=.jpg"` // Doesn't start with Username2 string `validate:"required,excludes=admin"` // Doesn't end with Filename string `validate:"required,excludes=.exe"` } fmt.Println("Test 3: Valid string lengths and patterns") validLength := LengthDTO{ Password: "securepass123", Username: "johndoe", PIN: "1234", Bio: "This is a bio that is long enough to pass validation rules", Description: "Learning Go programming", SafeInput: "This is safe input", BlogSlug: "blog-my-first-post", ImageFile: "profile.jpg", Username2: "johndoe", Filename: "document.pdf", } err = validate.Struct(validLength) if err == nil { fmt.Println("✅ All length validations passed") } fmt.Println() fmt.Println("Test 4: Invalid string lengths and patterns") invalidLength := LengthDTO{ Password: "short", // Too short Username: "verylongusernamethatexceedslimit", // Too long PIN: "12345", // Not exact length Bio: "Short", // Too short Description: "Learning Python", // Doesn't contain Go SafeInput: "alert<script>", // Contains <script> BlogSlug: "article-post", // Doesn't start with blog- ImageFile: "document.pdf", // Doesn't end with .jpg Username2: "administrator", // Contains admin Filename: "virus.exe", // Ends with .exe } err = validate.Struct(invalidLength) if err != nil { fmt.Println("❌ Validation failed:") for _, err := range err.(validator.ValidationErrors) { fmt.Printf(" - %s failed '%s'\n", err.Field(), err.Tag()) } } }

Run:

go run examples/string_validations.go

Key String Validations:

ValidationTagExample
Emailemailuser@example.com
URLurlhttps://example.com
AlphaalphaJohnDoe
Alphanumericalphanumuser123
UUIDuuid, uuid4550e8400-e29b-41d4-a716-446655440000
IP Addressip, ipv4, ipv6192.168.1.1
JSONjson{"key": "value"}
Base64base64SGVsbG8=
JWTjwtValid JWT token
Containscontains=textMust contain “text”
Starts withstartswith=prefixMust start with “prefix”
Ends withendswith=.jpgMust end with “.jpg”

Step 7: Number Validations {#step7-numbers}

7.1 Number Range and Comparison Validations

📝 Create examples/number_validations.go:

package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { validate := validator.New() fmt.Println("=== Number Validations ===\n") type NumberDTO struct { // Greater than or equal (>=) Age int `validate:"required,gte=18"` // Less than or equal (<=) Score int `validate:"required,lte=100"` // Greater than (>) Price float64 `validate:"required,gt=0"` // Less than (<) Discount float64 `validate:"required,lt=100"` // Range validation (between) Rating int `validate:"required,gte=1,lte=5"` // Equal to (==) ConfirmAge int `validate:"required,eqfield=Age"` // Not equal to (!=) NonZero int `validate:"required,ne=0"` // Quantity validation Quantity int `validate:"required,gte=1"` // Numeric string validation NumericString string `validate:"required,numeric"` // Integer validation Integer int `validate:"required,number"` // Positive number PositiveAmount float64 `validate:"required,gt=0"` // Negative number allowed Temperature int `validate:"required"` // Min and max for numbers Count int `validate:"required,min=1,max=100"` } // Valid example fmt.Println("Test 1: Valid numbers") valid := NumberDTO{ Age: 25, Score: 85, Price: 19.99, Discount: 10.5, Rating: 4, ConfirmAge: 25, NonZero: 5, Quantity: 15, NumericString: "12345", Integer: 42, PositiveAmount: 99.99, Temperature: -5, Count: 50, } err := validate.Struct(valid) if err == nil { fmt.Println("✅ All number validations passed") fmt.Printf(" Age: %d, Score: %d, Rating: %d\n", valid.Age, valid.Score, valid.Rating) } fmt.Println() // Invalid example fmt.Println("Test 2: Invalid numbers") invalid := NumberDTO{ Age: 15, // Under 18 Score: 105, // Over 100 Price: 0, // Not greater than 0 Discount: 100, // Not less than 100 Rating: 6, // Out of range ConfirmAge: 20, // Doesn't equal Age NonZero: 0, // Equal to 0 Quantity: 13, NumericString: "abc", // Not numeric Integer: 0, PositiveAmount: -5.00, // Not positive Temperature: 0, Count: 150, // Over max } err = validate.Struct(invalid) if err != nil { fmt.Println("❌ Validation failed:") for _, err := range err.(validator.ValidationErrors) { fmt.Printf(" - %s failed '%s' (value: %v, param: %s)\n", err.Field(), err.Tag(), err.Value(), err.Param()) } } fmt.Println() // Decimal-specific validations fmt.Println("=== Decimal Validations ===\n") type DecimalDTO struct { // Any decimal number Price float64 `validate:"required,numeric"` // Positive decimal Amount float64 `validate:"required,gt=0"` // Percentage (0-100) Percentage float64 `validate:"required,gte=0,lte=100"` // Money format (2 decimal places) - validated as string MoneyString string `validate:"required,numeric"` // Between specific decimal range Tax float64 `validate:"required,gte=0.00,lte=0.30"` } fmt.Println("Test 3: Valid decimals") validDecimal := DecimalDTO{ Price: 19.99, Amount: 100.50, Percentage: 75.5, MoneyString: "1234.56", Tax: 0.15, } err = validate.Struct(validDecimal) if err == nil { fmt.Println("✅ All decimal validations passed") fmt.Printf(" Price: $%.2f, Percentage: %.1f%%, Tax: %.2f\n", validDecimal.Price, validDecimal.Percentage, validDecimal.Tax) } fmt.Println() fmt.Println("Test 4: Invalid decimals") invalidDecimal := DecimalDTO{ Price: -5.00, Amount: 0, Percentage: 150.5, MoneyString: "not a number", Tax: 0.50, } err = validate.Struct(invalidDecimal) if err != nil { fmt.Println("❌ Validation failed:") for _, err := range err.(validator.ValidationErrors) { fmt.Printf(" - %s failed '%s'\n", err.Field(), err.Tag()) } } }

Key Number Validations:

ValidationTagExample
Greater than or equalgte=18Age >= 18
Less than or equallte=100Score <= 100
Greater thangt=0Price > 0
Less thanlt=100Discount < 100
Equaleq=5Must equal 5
Not equalne=0Cannot be 0
Numeric stringnumeric”123”, “123.45”

Step 8: Boolean Validations {#step8-booleans}

📝 Create examples/boolean_validations.go:

package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { validate := validator.New() fmt.Println("=== Boolean Validations ===\n") type BooleanDTO struct { // Must be true (accepted) AcceptTerms bool `validate:"required,eq=true"` // Must be false (declined) IsBlocked bool `validate:"eq=false"` // Any boolean value IsActive bool `validate:"boolean"` // Optional boolean (can be omitted) EmailVerified *bool `validate:"omitempty"` // Boolean with string representation BoolString string `validate:"omitempty,oneof=true false"` } // Test 1: Valid - accepted terms fmt.Println("Test 1: Valid booleans (accepted)") verified := true valid := BooleanDTO{ AcceptTerms: true, IsBlocked: false, IsActive: true, EmailVerified: &verified, BoolString: "true", } err := validate.Struct(valid) if err == nil { fmt.Println("✅ All boolean validations passed") fmt.Printf(" Terms accepted: %v, Active: %v\n", valid.AcceptTerms, valid.IsActive) } fmt.Println() // Test 2: Invalid - must accept terms fmt.Println("Test 2: Invalid booleans (terms not accepted)") invalid := BooleanDTO{ AcceptTerms: false, // Must be true IsBlocked: true, // Must be false IsActive: false, EmailVerified: nil, BoolString: "invalid", } err = validate.Struct(invalid) if err != nil { fmt.Println("❌ Validation failed:") for _, err := range err.(validator.ValidationErrors) { fmt.Printf(" - %s failed '%s' (current value: %v)\n", err.Field(), err.Tag(), err.Value()) } } fmt.Println() // Test 3: Conditional boolean validation fmt.Println("=== Conditional Boolean Validation ===\n") type ConditionalBoolDTO struct { // If newsletter is true, email must be provided Newsletter bool `validate:"boolean"` Email string `validate:"required_if=Newsletter true,omitempty,email"` } fmt.Println("Test 3: Newsletter with email") validConditional := ConditionalBoolDTO{ Newsletter: true, Email: "user@example.com", } err = validate.Struct(validConditional) if err == nil { fmt.Println("✅ Conditional validation passed") } fmt.Println() fmt.Println("Test 4: Newsletter without email (should fail)") invalidConditional := ConditionalBoolDTO{ Newsletter: true, Email: "", // Required when Newsletter is true } err = validate.Struct(invalidConditional) if err != nil { fmt.Println("❌ Validation failed:") for _, err := range err.(validator.ValidationErrors) { fmt.Printf(" - %s failed '%s'\n", err.Field(), err.Tag()) } } }

Key Boolean Validations:

ValidationTagUse Case
Must be trueeq=trueTerms acceptance
Must be falseeq=falseNot blocked
Any booleanbooleanSimple flags
Nullable*bool with omitemptyOptional flags

Step 9: Array Validations {#step9-arrays}

📝 Create examples/array_validations.go:

package main import ( "fmt" "github.com/go-playground/validator/v10" ) func main() { validate := validator.New() fmt.Println("=== Array/Slice Validations ===\n") type ArrayDTO struct { // Required array with size constraints Tags []string `validate:"required,min=1,max=5"` // Array where each element must be valid email Emails []string `validate:"required,dive,email"` // Unique elements (no duplicates) UniqueIDs []int `validate:"required,unique"` // Exact array size Ratings []int `validate:"required,len=3"` // Array with element range validation Scores []int `validate:"required,min=1,max=10,dive,gte=0,lte=100"` // Optional array OptionalTags []string `validate:"omitempty,dive,alpha"` // Array must contain specific value RequiredRoles []string `validate:"required,dive,oneof=admin user guest"` } // Valid example fmt.Println("Test 1: Valid arrays") valid := ArrayDTO{ Tags: []string{"go", "programming", "backend"}, Emails: []string{"user1@example.com", "user2@example.com"}, UniqueIDs: []int{1, 2, 3, 4, 5}, Ratings: []int{4, 5, 3}, Scores: []int{85, 90, 75, 88, 92}, OptionalTags: []string{"alpha", "beta"}, RequiredRoles: []string{"admin", "user"}, } err := validate.Struct(valid) if err == nil { fmt.Println("✅ All array validations passed") fmt.Printf(" Tags: %v\n", valid.Tags) fmt.Printf(" Scores: %v\n", valid.Scores) fmt.Printf(" Unique IDs: %v\n", valid.UniqueIDs) } fmt.Println() // Invalid example fmt.Println("Test 2: Invalid arrays") invalid := ArrayDTO{ Tags: []string{}, // Empty (min=1) Emails: []string{"not-an-email", "another-invalid"}, UniqueIDs: []int{1, 2, 2, 3}, // Has duplicate Ratings: []int{4, 5}, // Wrong length (need 3) Scores: []int{85, 105, 75}, // 105 exceeds max OptionalTags: []string{"tag1", "tag2!"}, // Has non-alpha RequiredRoles: []string{"superadmin"}, // Not in enum } err = validate.Struct(invalid) if err != nil { fmt.Println("❌ Validation failed:") for _, err := range err.(validator.ValidationErrors) { fmt.Printf(" - %s failed '%s'\n", err.Field(), err.Tag()) } } }

Key Array Validations:

ValidationTagExample
Min lengthmin=1At least 1 element
Max lengthmax=5At most 5 elements
Exact lengthlen=3Exactly 3 elements
Unique elementsuniqueNo duplicates
Validate eachdive,emailEach must be valid email