Skip to main content
Version: 0.1.1

Configuration Options

Customize Pedantigo's validation behavior with options.

Overview

Pedantigo offers configuration options to customize how validators behave during JSON unmarshaling. Options are passed when creating explicit validators:

// Simple API: automatic defaults
user, err := pedantigo.Unmarshal[User](jsonData)

// Validator API: customize behavior
validator := pedantigo.New[User](pedantigo.ValidatorOptions{
StrictMissingFields: true,
ExtraFields: pedantigo.ExtraForbid,
})
user, err := validator.Unmarshal(jsonData)

ValidatorOptions Type

Configuration is provided via the ValidatorOptions struct:

type ValidatorOptions struct {
// StrictMissingFields controls behavior for missing fields
// Default: true (missing fields without defaults cause errors)
StrictMissingFields bool

// ExtraFields controls how unknown JSON fields are handled
// Default: ExtraIgnore (unknown fields are silently ignored)
ExtraFields ExtraFieldsMode
}

Default Options

The default options are optimized for safety and strictness:

pedantigo.DefaultValidatorOptions()
// Returns: ValidatorOptions{
// StrictMissingFields: true,
// ExtraFields: ExtraIgnore,
// }

StrictMissingFields Option

Controls whether missing fields (fields not present in JSON) cause validation errors.

StrictMissingFields: true (Default)

Missing fields without defaults are validation errors:

type Config struct {
Host string `json:"host" pedantigo:"required"`
Port int `json:"port"` // No default
}

jsonData := []byte(`{"host":"localhost"}`)
validator := pedantigo.New[Config](pedantigo.ValidatorOptions{
StrictMissingFields: true,
})

config, err := validator.Unmarshal(jsonData)
// Error: port field is missing and has no default

Use this mode when you want to:

  • Catch missing fields as validation errors
  • Ensure all fields are explicitly provided in JSON
  • Enforce strict API contracts

StrictMissingFields: false

Missing fields without defaults are left as zero values:

type Config struct {
Host string `json:"host" pedantigo:"required"`
Port int `json:"port"` // Zero value: 0
}

jsonData := []byte(`{"host":"localhost"}`)
validator := pedantigo.New[Config](pedantigo.ValidatorOptions{
StrictMissingFields: false,
})

config, err := validator.Unmarshal(jsonData)
// Success: Port is set to 0 (zero value)
fmt.Println(config.Port) // Output: 0

Use this mode when you want to:

  • Allow partial JSON input (missing fields get zero values)
  • Make most fields optional
  • Support backwards-compatible API evolution

ExtraFields Option

Controls how unknown (extra) JSON fields are handled during unmarshaling.

ExtraIgnore (Default)

Unknown JSON fields are silently ignored:

type User struct {
Name string `json:"name"`
Email string `json:"email"`
}

jsonData := []byte(`{
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"phone": "555-1234"
}`)

validator := pedantigo.New[User](pedantigo.ValidatorOptions{
ExtraFields: pedantigo.ExtraIgnore,
})

user, err := validator.Unmarshal(jsonData)
// Success: age and phone fields are ignored

Use this mode when you want to:

  • Accept JSON with additional fields (future-proofing)
  • Ignore client-provided metadata
  • Support flexible API clients

ExtraForbid

Unknown JSON fields cause validation errors:

type User struct {
Name string `json:"name"`
Email string `json:"email"`
}

jsonData := []byte(`{
"name": "Alice",
"email": "alice@example.com",
"age": 30
}`)

validator := pedantigo.New[User](pedantigo.ValidatorOptions{
ExtraFields: pedantigo.ExtraForbid,
})

user, err := validator.Unmarshal(jsonData)
// Error: unknown field "age" in JSON
var validationErr *pedantigo.ValidationError
if errors.As(err, &validationErr) {
fmt.Println(validationErr.Errors[0].Message)
// Output: unknown field in JSON
}

Use this mode when you want to:

  • Prevent typos in client input
  • Detect API misuse
  • Enforce strict schema compliance

Complete Examples

API Server with Strict Validation

package main

import (
"encoding/json"
"errors"
"log"

"github.com/smrutai/pedantigo"
)

type CreateUserRequest struct {
Username string `json:"username" pedantigo:"required,min=3,max=20"`
Email string `json:"email" pedantigo:"required,email"`
Password string `json:"password" pedantigo:"required,min=8"`
}

// API validator: strict about extra fields, requires all fields
var apiValidator = pedantigo.New[CreateUserRequest](
pedantigo.ValidatorOptions{
StrictMissingFields: true,
ExtraFields: pedantigo.ExtraForbid,
},
)

func handleCreateUser(jsonData []byte) (*CreateUserRequest, error) {
req, err := apiValidator.Unmarshal(jsonData)
if err != nil {
var validationErr *pedantigo.ValidationError
if errors.As(err, &validationErr) {
return nil, validationErr
}
return nil, err
}
return req, nil
}

func main() {
// Valid request
valid := []byte(`{
"username": "alice",
"email": "alice@example.com",
"password": "securepass123"
}`)

user, err := handleCreateUser(valid)
if err != nil {
log.Fatalf("Validation failed: %v", err)
}
log.Printf("Created user: %s\n", user.Username)

// Invalid: missing field
invalid := []byte(`{
"username": "bob",
"email": "bob@example.com"
}`)

_, err = handleCreateUser(invalid)
// Error: password field is required

// Invalid: extra fields
typo := []byte(`{
"username": "charlie",
"email": "charlie@example.com",
"password": "securepass123",
"user_id": 42
}`)

_, err = handleCreateUser(typo)
// Error: unknown field "user_id" in JSON
}

Configuration File Parsing (Lenient)

type AppConfig struct {
Database struct {
Host string `json:"host" pedantigo:"required"`
Port int `json:"port" pedantigo:"default=5432"`
Username string `json:"username" pedantigo:"required"`
Password string `json:"password" pedantigo:"required"`
} `json:"database"`
Server struct {
Addr string `json:"addr" pedantigo:"default=0.0.0.0:8080"`
} `json:"server"`
}

// Config validator: lenient about missing fields, allows extra fields
var configValidator = pedantigo.New[AppConfig](
pedantigo.ValidatorOptions{
StrictMissingFields: false, // Missing fields get zero values
ExtraFields: pedantigo.ExtraIgnore, // Extra fields ignored
},
)

func loadConfig(jsonData []byte) (*AppConfig, error) {
config, err := configValidator.Unmarshal(jsonData)
if err != nil {
// Still check for validation errors
var validationErr *pedantigo.ValidationError
if errors.As(err, &validationErr) {
return nil, validationErr
}
return nil, err
}
return config, nil
}

func main() {
// Minimal config: missing fields use defaults
jsonData := []byte(`{
"database": {
"host": "db.example.com",
"username": "admin",
"password": "secret"
}
}`)

config, err := loadConfig(jsonData)
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}

// config.Server.Addr is "0.0.0.0:8080" (default)
// config.Database.Port is 5432 (default)
// Extra fields in config are ignored
}

Migration-Friendly API

type UserV2 struct {
ID string `json:"id" pedantigo:"required,uuid"`
Username string `json:"username" pedantigo:"required"`
Email string `json:"email" pedantigo:"required,email"`
// New field: optional during migration
Status string `json:"status" pedantigo:"default=active"`
}

// Migration validator: accept both old and new client requests
var migrationValidator = pedantigo.New[UserV2](
pedantigo.ValidatorOptions{
StrictMissingFields: false, // Old clients don't send 'status'
ExtraFields: pedantigo.ExtraIgnore, // Old clients send extra fields
},
)

func handleUserUpdate(jsonData []byte) (*UserV2, error) {
user, err := migrationValidator.Unmarshal(jsonData)
if err != nil {
var validationErr *pedantigo.ValidationError
if errors.As(err, &validationErr) {
return nil, validationErr
}
return nil, err
}
return user, nil
}

func main() {
// Old client request (missing 'status' field)
oldClient := []byte(`{
"id": "123e4567-e89b-12d3-a456-426614174000",
"username": "alice",
"email": "alice@example.com"
}`)

user, err := handleUserUpdate(oldClient)
// Success: Status defaults to "active"

// New client request (includes 'status', sends extra metadata)
newClient := []byte(`{
"id": "123e4567-e89b-12d3-a456-426614174000",
"username": "bob",
"email": "bob@example.com",
"status": "inactive",
"metadata": {"source": "mobile"}
}`)

user, err = handleUserUpdate(newClient)
// Success: metadata field is ignored
}

Choosing the Right Options

ScenarioStrictMissingFieldsExtraFieldsReason
REST API with strict contractstrueExtraForbidPrevent bugs from typos/API misuse
Configuration file parsingfalseExtraIgnoreAllow partial configs, future-proof
During API migrationfalseExtraIgnoreAccept old and new client versions
Public web APItrueExtraIgnoreStrict input, future-proof
Internal service APItrueExtraForbidStrict both ways
Webhook receiverfalseExtraIgnoreAccept any fields from sender

Option Combinations

Most Strict

pedantigo.ValidatorOptions{
StrictMissingFields: true,
ExtraFields: pedantigo.ExtraForbid,
}
// Best for: REST API validation, ensuring exact schema match

Most Lenient

pedantigo.ValidatorOptions{
StrictMissingFields: false,
ExtraFields: pedantigo.ExtraIgnore,
}
// Best for: Configuration parsing, webhook receivers, flexible APIs

Balanced (Default)

pedantigo.DefaultValidatorOptions()
// StrictMissingFields: true
// ExtraFields: ExtraIgnore
// Best for: General-purpose APIs, good balance of safety and flexibility

API Comparison

Simple API (Uses Defaults)

// Always uses DefaultValidatorOptions()
user, err := pedantigo.Unmarshal[User](jsonData)

// Equivalent to:
validator := pedantigo.New[User](pedantigo.DefaultValidatorOptions())
user, err := validator.Unmarshal(jsonData)

Validator API (Customizable)

// Create validator with custom options
validator := pedantigo.New[User](pedantigo.ValidatorOptions{
StrictMissingFields: true,
ExtraFields: pedantigo.ExtraForbid,
})

// Reuse the same validator multiple times
user1, err := validator.Unmarshal(data1)
user2, err := validator.Unmarshal(data2)

// Schema is cached, so subsequent calls are very fast
schema := validator.Schema()

Use the Validator API when you need:

  • Custom options
  • Schema caching for high-performance scenarios
  • Consistent behavior across multiple unmarshal calls
  • Explicit control over validator creation
info

See Validator API for more details on the full API.

tip

For most use cases, the Simple API with default options is sufficient. Use the Validator API only when you need custom behavior or performance optimization.