Skip to main content
Version: 2.0.0

Validator API

The Validator API provides advanced configuration and performance optimization for validation workflows. This is the underlying API used by the Simple API and offers fine-grained control for specialized use cases.

Simple API Recommended

For most applications, use the Simple API instead. It provides automatic caching and requires less code.

Overview​

The Validator API creates reusable validator instances with custom configuration. Each validator is specific to a struct type and applies the same rules consistently across requests.

When to Use Validator API​

  • Need custom validation options (StrictMissingFields, ExtraForbid)
  • Reusing the same validator across many requests
  • Performance-critical paths (avoid global cache lookup)
  • Implementing custom validator registration
  • Building discriminated unions (advanced feature)

Creating a Validator​

Default Options​

import "github.com/SmrutAI/pedantigo/v2/validator"

type User struct {
Email string `validate:"required,email"`
Age int `validate:"required,min=18"`
}

// Create validator with default options
userValidator := validator.New[User]()

Default options:

  • StrictMissingFields: true - Missing required fields are errors
  • ExtraFields: ExtraIgnore - Unknown JSON fields are silently ignored

Custom Options​

import "github.com/SmrutAI/pedantigo/v2/validator"

// Create with custom options
userValidator := validator.New[User](validator.Options{
StrictMissingFields: false, // Allow missing fields (use pointers for optional)
ExtraFields: validator.ExtraForbid, // Reject unknown fields
})

Options​

type Options struct {
// StrictMissingFields controls whether missing fields without defaults cause errors
// Default: true (missing fields are errors)
// Set to false if using pointers for optional fields
StrictMissingFields bool

// ExtraFields controls how unknown JSON fields are handled during Unmarshal
// Options:
// - ExtraIgnore (default): Unknown fields are silently ignored
// - ExtraForbid: Unknown fields cause validation errors
// - ExtraAllow: Reserved for future use
ExtraFields ExtraFieldsMode
}

Validator Methods​

Unmarshal​

Unmarshal JSON data and validate it in a single operation.

userValidator := validator.New[User]()

// Unmarshal JSON and validate
user, err := userValidator.Unmarshal([]byte(`{"email": "test@example.com", "age": 25}`))
if err != nil {
// Handle validation error
fmt.Printf("Validation failed: %v\n", err)
}

// Access validated data
fmt.Printf("Email: %s, Age: %d\n", user.Email, user.Age)

Behavior:

  • Parses JSON and validates fields according to struct tags
  • Applies defaults and defaultFactory functions
  • Returns error slice on validation failure
  • Reuses field deserializers for efficiency

Validate​

Validate an existing struct instance.

userValidator := validator.New[User]()

user := &User{
Email: "invalid-email",
Age: 15,
}

err := userValidator.Validate(user)
if err != nil {
// Handle validation error
fmt.Printf("Validation failed: %v\n", err)
}

Use Case: Post-construction validation or programmatic struct creation.

NewModel​

Create a validated instance from various input types.

userValidator := validator.New[User]()

// From JSON bytes
user, err := userValidator.NewModel([]byte(`{"email": "test@example.com", "age": 25}`))

// From map (kwargs pattern)
user, err := userValidator.NewModel(map[string]any{
"email": "test@example.com",
"age": 25,
})

// From existing struct (validates it)
existing := User{Email: "test@example.com", Age: 25}
user, err := userValidator.NewModel(existing)

// From pointer
existing := &User{Email: "test@example.com", Age: 25}
user, err := userValidator.NewModel(existing)

Accepts:

  • []byte - JSON data
  • map[string]any - Field values
  • T - Struct value
  • *T - Struct pointer

Schema Methods​

Schema​

Get the JSON Schema as a Go object.

userValidator := validator.New[User]()

schema := userValidator.Schema()
// schema is *jsonschema.Schema

// Access schema properties
fmt.Printf("Title: %s\n", schema.Title)
fmt.Printf("Type: %s\n", schema.Type)

Caching: the schema is generated once and cached on the validator instance — see Performance Optimization for the measured cost.

SchemaJSON​

Get the JSON Schema as JSON bytes.

userValidator := validator.New[User]()

schemaBytes, err := userValidator.SchemaJSON()
if err != nil {
// Handle error
}

// Use for API documentation, storage, etc.
fmt.Println(string(schemaBytes))

SchemaOpenAPI​

Get OpenAPI 3.1 compatible component schema.

userValidator := validator.New[User]()

schema := userValidator.SchemaOpenAPI()
// Returns component schema with $defs (OpenAPI 3.1 / JSON Schema Draft 2020-12)

SchemaJSONOpenAPI​

Get OpenAPI 3.1 compatible component schema as JSON bytes.

userValidator := validator.New[User]()

schemaBytes, err := userValidator.SchemaJSONOpenAPI()
if err != nil {
// Handle error
}
// Embed in OpenAPI 3.1 spec under components/schemas

SchemaLLM​

Get JSON Schema optimized for LLM APIs (no $schema or $id fields).

schema := validator.SchemaLLM()
// Returns schema without $schema or $id fields for LLM tool calling

Both $schema and $id are cleared because some LLMs (like Groq) echo schema metadata fields back in their responses, causing JSON parsing failures.

SchemaJSONLLM​

Get JSON Schema as JSON bytes for LLM APIs.

userValidator := validator.New[User]()

schemaBytes, err := userValidator.SchemaJSONLLM()
if err != nil {
// Handle error
}
// JSON has no $schema or $id fields - cleaner for LLM integration

Marshal Methods​

Validate and convert struct to JSON.

userValidator := validator.New[User]()

user := &User{
Email: "test@example.com",
Age: 25,
}

// With default options
jsonData, err := userValidator.Marshal(user)
if err != nil {
// Handle validation or marshal error
}

// With custom options (context-based field exclusion)
opts := validator.ForContext("api") // Excludes fields marked with exclude:api
jsonData, err := validator.MarshalWithOptions(user, opts)

Behavior:

  • Validates struct before marshaling
  • Supports context-based field inclusion/exclusion
  • Supports omitzero tag for sparse output

Dict​

Convert struct to map.

user := &User{
Email: "test@example.com",
Age: 25,
}

dict, err := validator.Dict(user)
if err != nil {
// Handle error
}

// Access as map
fmt.Printf("Email: %s\n", dict["email"])
fmt.Printf("Age: %d\n", dict["age"])

Use Case: API responses, dynamic field access, data transformation.

Usage Patterns​

Reusing Validators​

Create once, use many times for best performance:

// At initialization
userValidator := validator.New[User]()

// In request handler
func handleUserCreation(w http.ResponseWriter, r *http.Request) {
var data []byte
// ... read request body ...

user, err := userValidator.Unmarshal(data)
if err != nil {
// Handle error
}
// ... process user ...
}

// Reuse same validator for all requests
func handleUserUpdate(w http.ResponseWriter, r *http.Request) {
var data []byte
// ... read request body ...

user, err := userValidator.Unmarshal(data)
if err != nil {
// Handle error
}
// ... process user ...
}

Multiple Validators​

Use different validators with different configurations:

// Strict validation for admin operations
adminValidator := validator.New[User](validator.Options{
StrictMissingFields: true,
ExtraFields: validator.ExtraForbid,
})

// Lenient validation for imports
importValidator := validator.New[User](validator.Options{
StrictMissingFields: false,
ExtraFields: validator.ExtraIgnore,
})

// Use as appropriate
adminUser, err := adminValidator.Unmarshal(data)
importedUser, err := importValidator.Unmarshal(data)

Schema Caching​

Validators cache schemas internally per type — see Performance Optimization for the measured cost:

// First call - generates and caches the schema
schema1 := userValidator.Schema()

// Subsequent calls - returned from cache
schema2 := userValidator.Schema()

// Same cached schema is returned
fmt.Println(schema1 == schema2) // true

Performance Considerations​

New() does real, one-time work — parses struct tags, builds field deserializers, and sets up cross-field validation — so create a validator once at initialization and reuse it, rather than calling New() per request. See Performance Optimization for the actual measured cost of New(), the real (negligible) difference between the Simple API and Validator API for repeated calls, and the caching patterns that make either fast.

Comparison with Simple API​

FeatureValidator APISimple API
SetupExplicit instanceAutomatic caching
ConfigurationCustom optionsDefault only
ReusabilityManual managementAutomatic
Use CaseHigh-throughput, custom configGeneral purpose
Code ExampleuserValidator.Unmarshal(data)validator.Unmarshal[User](data)

When to Switch to Simple API​

If any of these apply, use the Simple API instead:

  • Configuration not needed (using defaults)
  • Multiple different types validated
  • Simplicity over micro-optimization
  • General-purpose application code

When to Use Validator API​

If any of these apply, use the Validator API:

  • High-throughput request handler
  • Custom validator options needed
  • Validator reused across many requests
  • Building a framework or library
  • Discriminated unions or advanced features

Error Handling​

Both methods return errors on validation failure:

userValidator := validator.New[User]()

user, err := userValidator.Unmarshal(data)
if err != nil {
// err is a validation error
fmt.Printf("Validation failed: %v\n", err)
}

// Check specific validation errors
errs := user, err // err is a FieldError slice
for _, fieldErr := range errs {
fmt.Printf("Field %s: %s\n", fieldErr.Field, fieldErr.Message)
}

See Errors for detailed error handling.

Advanced Topics​

Custom Validator Registration​

Register custom validation functions per validator instance:

userValidator := validator.New[User]()
// Custom validators can be registered at validator creation
// See Options for details

Discriminated Unions​

For complex validation scenarios with union types:

// Create union validator (advanced feature)
unionValidator := validator.NewUnion[T](opts...)

Refer to advanced examples for union validation patterns.

Complete Example​

package main

import (
"fmt"
"github.com/SmrutAI/pedantigo/v2/validator"
)

type User struct {
Email string `validate:"required,email"`
Age int `validate:"required,min=18,max=120"`
Username string `validate:"required,min=3,max=50"`
}

func main() {
// Create validator with custom options
userValidator := validator.New[User](validator.Options{
StrictMissingFields: true,
ExtraFields: validator.ExtraForbid,
})

// Example 1: Unmarshal JSON
jsonData := []byte(`{
"email": "alice@example.com",
"age": 28,
"username": "alice_wonder"
}`)

user, err := userValidator.Unmarshal(jsonData)
if err != nil {
fmt.Printf("Validation error: %v\n", err)
return
}
fmt.Printf("Valid user: %+v\n", user)

// Example 2: Validate existing struct
invalidUser := &User{
Email: "not-an-email",
Age: 17,
Username: "ab",
}

err = userValidator.Validate(invalidUser)
if err != nil {
fmt.Printf("Validation error: %v\n", err)
}

// Example 3: Get schema
schema := userValidator.Schema()
fmt.Printf("Schema: %+v\n", schema)

// Example 4: Marshal to JSON
validUser := &User{
Email: "bob@example.com",
Age: 30,
Username: "bob_builder",
}

jsonOutput, err := userValidator.Marshal(validUser)
if err != nil {
fmt.Printf("Marshal error: %v\n", err)
return
}
fmt.Printf("JSON output: %s\n", string(jsonOutput))
}

See Also​