structs

package
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2025 License: BSD-3-Clause Imports: 5 Imported by: 0

README

Structs

The structs package provides comprehensive utilities for struct reflection, introspection, validation, and manipulation in Go. It offers type-safe operations for analyzing struct fields, tags, and values, making it easier to work with structs dynamically.

Features

  • Type Introspection: Get detailed type information including package names and kinds
  • Field Analysis: Access field names, values, and existence checks
  • Struct Tag Support: Extract and analyze struct tags for any key
  • Validation: Validate that all struct fields contain non-zero values
  • Cloning: Deep copy structs using JSON serialization
  • Nil Checking: Safe nil checking for pointers and interface types
  • Type Safety: All operations are type-safe with proper error handling

Quick Start

package main

import (
    "fmt"
    "github.com/alextanhongpin/core/types/structs"
)

type User struct {
    ID    int    `json:"id" validate:"required"`
    Name  string `json:"name" validate:"required"`
    Email string `json:"email" validate:"required,email"`
    Active bool   `json:"active" validate:"required"`
}

func main() {
    user := User{ID: 1, Name: "Alice", Email: "[email protected]", Active: true}
    
    // Type introspection
    fmt.Println("Type:", structs.Type(user))     // Type: main.User
    fmt.Println("Name:", structs.Name(user))     // Name: User
    fmt.Println("Kind:", structs.Kind(user))     // Kind: struct
    
    // Field operations
    names, _ := structs.GetFieldNames(user)
    fmt.Println("Fields:", names)                // Fields: [ID Name Email Active]
    
    // Validation
    if err := structs.NonZero(user); err != nil {
        fmt.Println("Validation failed:", err)
    } else {
        fmt.Println("All fields are valid!")    // All fields are valid!
    }
}

API Reference

Type Introspection
user := User{ID: 1, Name: "Alice"}

// Get type information
fullType := structs.Type(user)           // "main.User"
pkgName := structs.PkgName(user)         // "main.User" 
simpleName := structs.Name(user)         // "User"
kind := structs.Kind(user)               // reflect.Struct

// Type checking
isStruct := structs.IsStruct(user)       // true
isPointer := structs.IsPointer(&user)    // true
isNil := structs.IsNil((*User)(nil))     // true
Field Operations
// Check field existence
hasName := structs.HasField(user, "Name")        // true
hasPassword := structs.HasField(user, "Password") // false

// Get field names
names, err := structs.GetFieldNames(user)        // ["ID", "Name", "Email"]

// Get specific field value
name, err := structs.GetFieldValue(user, "Name") // "Alice"

// Get all fields as map
fields, err := structs.GetFields(user)           // map[string]any
Struct Tags
// Get tags by key
jsonTags, err := structs.GetTags(user, "json")      // map[field]tag
validateTags, err := structs.GetTags(user, "validate") // map[field]tag

// Example output:
// jsonTags: {"ID": "id", "Name": "name", "Email": "email"}
// validateTags: {"ID": "required", "Name": "required", "Email": "required,email"}
Validation
// Validate all fields are non-zero
err := structs.NonZero(user)
if err != nil {
    var fieldErr *structs.FieldError
    if errors.As(err, &fieldErr) {
        fmt.Printf("Empty field: %s (path: %s)\n", fieldErr.Field, fieldErr.Path)
    }
}
Cloning
// Deep clone a struct
original := User{ID: 1, Name: "Alice"}
cloned, err := structs.Clone(original)

// Modifications to original don't affect clone
original.Name = "Bob"
fmt.Println(cloned.Name) // Still "Alice"

Real-World Examples

Configuration Validation
type DatabaseConfig struct {
    Host     string `json:"host" validate:"required"`
    Port     int    `json:"port" validate:"required,min=1,max=65535"`
    Username string `json:"username" validate:"required"`
    Password string `json:"password" validate:"required"`
    Database string `json:"database" validate:"required"`
    SSL      bool   `json:"ssl"`
}

type Config struct {
    Database DatabaseConfig `json:"database"`
    Redis    struct {
        Host string `json:"host" validate:"required"`
        Port int    `json:"port" validate:"required"`
    } `json:"redis"`
    Server struct {
        Port int    `json:"port" validate:"required"`
        Host string `json:"host"`
    } `json:"server"`
}

func ValidateConfig(config Config) error {
    // Validate all required fields are present
    if err := structs.NonZero(config); err != nil {
        var fieldErr *structs.FieldError
        if errors.As(err, &fieldErr) {
            return fmt.Errorf("missing required configuration: %s", fieldErr.Field)
        }
        return err
    }
    
    // Additional validation can be added here
    return nil
}

// Usage
config := Config{
    Database: DatabaseConfig{
        Host:     "localhost",
        Port:     5432,
        Username: "admin",
        Password: "secret",
        Database: "myapp",
        SSL:      true,
    },
}
config.Redis.Host = "localhost"
config.Redis.Port = 6379
config.Server.Port = 8080

if err := ValidateConfig(config); err != nil {
    log.Fatal("Configuration error:", err)
}
Dynamic API Response Processing
type APIResponse struct {
    Success bool                   `json:"success"`
    Data    map[string]interface{} `json:"data,omitempty"`
    Error   string                 `json:"error,omitempty"`
    Meta    struct {
        Page      int `json:"page"`
        PerPage   int `json:"per_page"`
        Total     int `json:"total"`
        TotalPages int `json:"total_pages"`
    } `json:"meta,omitempty"`
}

func ProcessAPIResponse(response APIResponse) error {
    // Check response structure
    fmt.Printf("Response type: %s\n", structs.Name(response))
    
    // Get all non-empty fields
    fields, err := structs.GetFields(response)
    if err != nil {
        return err
    }
    
    // Process based on available fields
    if success, ok := fields["success"].(bool); ok {
        if success {
            fmt.Println("✓ API call successful")
            
            // Process data if present
            if _, hasData := fields["data"]; hasData {
                fmt.Println("✓ Response contains data")
            }
            
            // Process pagination if present
            if _, hasMeta := fields["meta"]; hasMeta {
                fmt.Println("✓ Response contains pagination info")
            }
        } else {
            // Check for error message
            if errorMsg, hasError := fields["error"].(string); hasError {
                return fmt.Errorf("API error: %s", errorMsg)
            }
            return fmt.Errorf("API call failed with no error message")
        }
    }
    
    return nil
}
Struct Tag-Based Serialization
type Product struct {
    ID          int     `json:"id" db:"product_id" csv:"ID"`
    Name        string  `json:"name" db:"product_name" csv:"Name"`
    Price       float64 `json:"price" db:"price" csv:"Price"`
    Description string  `json:"description" db:"description" csv:"Description"`
    InStock     bool    `json:"in_stock" db:"in_stock" csv:"In Stock"`
    CreatedAt   time.Time `json:"created_at" db:"created_at" csv:"Created"`
}

func GetFieldMapping(product Product, format string) (map[string]string, error) {
    // Get field mappings for different formats
    tags, err := structs.GetTags(product, format)
    if err != nil {
        return nil, err
    }
    
    // Create field name to tag mapping
    mapping := make(map[string]string)
    fieldNames, _ := structs.GetFieldNames(product)
    
    for _, fieldName := range fieldNames {
        if tag, exists := tags[fieldName]; exists {
            mapping[fieldName] = tag
        } else {
            mapping[fieldName] = fieldName // Use field name as fallback
        }
    }
    
    return mapping, nil
}

// Usage
product := Product{ID: 1, Name: "Laptop", Price: 999.99}

// Get JSON field mappings
jsonMapping, _ := GetFieldMapping(product, "json")
// Result: {"ID": "id", "Name": "name", "Price": "price", ...}

// Get database column mappings  
dbMapping, _ := GetFieldMapping(product, "db")
// Result: {"ID": "product_id", "Name": "product_name", ...}

// Get CSV header mappings
csvMapping, _ := GetFieldMapping(product, "csv")
// Result: {"ID": "ID", "Name": "Name", "InStock": "In Stock", ...}
Form Validation with Error Details
type UserForm struct {
    FirstName string `json:"first_name" validate:"required,min=2"`
    LastName  string `json:"last_name" validate:"required,min=2"`
    Email     string `json:"email" validate:"required,email"`
    Age       int    `json:"age" validate:"min=13,max=120"`
    Terms     bool   `json:"terms" validate:"required"`
}

type ValidationError struct {
    Field   string `json:"field"`
    Message string `json:"message"`
    Value   any    `json:"value"`
}

func ValidateUserForm(form UserForm) []ValidationError {
    var errors []ValidationError
    
    // Check for empty required fields
    if err := structs.NonZero(form); err != nil {
        var fieldErr *structs.FieldError
        if errors.As(err, &fieldErr) {
            errors = append(errors, ValidationError{
                Field:   fieldErr.Field,
                Message: "This field is required",
                Value:   nil,
            })
        }
    }
    
    // Get validation rules from tags
    rules, _ := structs.GetTags(form, "validate")
    
    // Validate each field against its rules
    for fieldName, rule := range rules {
        value, _ := structs.GetFieldValue(form, fieldName)
        
        // Simple validation logic (in real apps, use a validation library)
        if strings.Contains(rule, "email") {
            if email, ok := value.(string); ok {
                if !strings.Contains(email, "@") {
                    errors = append(errors, ValidationError{
                        Field:   fieldName,
                        Message: "Must be a valid email address",
                        Value:   email,
                    })
                }
            }
        }
        
        if strings.Contains(rule, "min=") {
            // Extract min value and validate
            // Implementation details omitted for brevity
        }
    }
    
    return errors
}
Object Builder with Validation
type ProductBuilder struct {
    product Product
}

func NewProductBuilder() *ProductBuilder {
    return &ProductBuilder{product: Product{}}
}

func (pb *ProductBuilder) WithID(id int) *ProductBuilder {
    pb.product.ID = id
    return pb
}

func (pb *ProductBuilder) WithName(name string) *ProductBuilder {
    pb.product.Name = name
    return pb
}

func (pb *ProductBuilder) WithPrice(price float64) *ProductBuilder {
    pb.product.Price = price
    return pb
}

func (pb *ProductBuilder) Build() (Product, error) {
    // Validate that all required fields are set
    if err := structs.NonZero(pb.product); err != nil {
        var fieldErr *structs.FieldError
        if errors.As(err, &fieldErr) {
            return Product{}, fmt.Errorf("missing required field: %s", fieldErr.Field)
        }
        return Product{}, err
    }
    
    // Additional business logic validation
    if pb.product.Price <= 0 {
        return Product{}, fmt.Errorf("price must be positive")
    }
    
    return pb.product, nil
}

// Usage
product, err := NewProductBuilder().
    WithID(1).
    WithName("Gaming Laptop").
    WithPrice(1299.99).
    Build()

if err != nil {
    fmt.Printf("Failed to build product: %v\n", err)
} else {
    fmt.Printf("Created product: %+v\n", product)
}
Dynamic Struct Copying
func CopyNonZeroFields(src, dst any) error {
    if !structs.IsStruct(src) || !structs.IsStruct(dst) {
        return fmt.Errorf("both src and dst must be structs")
    }
    
    // Get source fields
    srcFields, err := structs.GetFields(src)
    if err != nil {
        return err
    }
    
    // Get destination field names to check compatibility
    dstNames, err := structs.GetFieldNames(dst)
    if err != nil {
        return err
    }
    
    dstNameSet := make(map[string]bool)
    for _, name := range dstNames {
        dstNameSet[name] = true
    }
    
    // Copy non-zero fields that exist in destination
    srcNames, _ := structs.GetFieldNames(src)
    for _, fieldName := range srcNames {
        if !dstNameSet[fieldName] {
            continue // Skip fields that don't exist in destination
        }
        
        srcValue, err := structs.GetFieldValue(src, fieldName)
        if err != nil {
            continue
        }
        
        // Check if source field is non-zero
        if !structs.isEmpty(srcValue) {
            // In a real implementation, you'd use reflection to set the field
            fmt.Printf("Would copy %s: %v\n", fieldName, srcValue)
        }
    }
    
    return nil
}
Audit Logging
type AuditEntry struct {
    Timestamp time.Time              `json:"timestamp"`
    Action    string                 `json:"action"`
    Entity    string                 `json:"entity"`
    EntityID  string                 `json:"entity_id"`
    Changes   map[string]interface{} `json:"changes"`
    User      string                 `json:"user"`
}

func CreateAuditLog(entity any, action, user string) AuditEntry {
    entry := AuditEntry{
        Timestamp: time.Now(),
        Action:    action,
        Entity:    structs.Name(entity),
        User:      user,
    }
    
    // Get entity ID if it has one
    if id, err := structs.GetFieldValue(entity, "ID"); err == nil {
        entry.EntityID = fmt.Sprintf("%v", id)
    }
    
    // Capture all field values as changes
    if fields, err := structs.GetFields(entity); err == nil {
        entry.Changes = fields
    }
    
    return entry
}

// Usage
user := User{ID: 1, Name: "Alice", Email: "[email protected]"}
auditEntry := CreateAuditLog(user, "CREATE", "admin")
fmt.Printf("Audit: %+v\n", auditEntry)

Best Practices

  1. Validation: Use NonZero() for basic required field validation
  2. Error Handling: Always check for FieldError when validation fails
  3. Performance: Cache reflection results for frequently accessed structs
  4. Type Safety: Verify struct types before performing operations
  5. Tag Conventions: Use consistent tag naming across your application
  6. Clone Limitations: JSON-based cloning only works with JSON-serializable fields

Performance Considerations

  • Reflection Cost: Struct introspection uses reflection, which has overhead
  • JSON Cloning: Clone() uses JSON marshaling, which may be slower than manual copying
  • Caching: Consider caching type information for frequently accessed structs
  • Field Access: Direct field access is faster than GetFieldValue() when possible

Limitations

  1. Private Fields: Only exported (public) fields are accessible
  2. JSON Cloning: Clone() only copies JSON-serializable fields
  3. Type Conversions: Some operations require type assertions
  4. Reflection Overhead: Performance impact for high-frequency operations

Thread Safety

The structs package functions are read-only and thread-safe for concurrent access to the same struct values. However, modifying struct values while performing operations is not safe without external synchronization.

Integration Examples

With Validation Libraries
import "github.com/go-playground/validator/v10"

func ValidateStruct(s any) error {
    // First check for non-zero fields
    if err := structs.NonZero(s); err != nil {
        return fmt.Errorf("required field validation failed: %w", err)
    }
    
    // Then use a proper validation library
    validate := validator.New()
    return validate.Struct(s)
}
With ORM Integration
func GetDBColumnName(entity any, fieldName string) (string, error) {
    dbTags, err := structs.GetTags(entity, "db")
    if err != nil {
        return "", err
    }
    
    if columnName, exists := dbTags[fieldName]; exists {
        return columnName, nil
    }
    
    return fieldName, nil // Default to field name
}

Documentation

Overview

Package structs provides utilities for struct reflection, validation, and analysis. It offers type introspection, field validation, and struct manipulation helpers.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Clone

func Clone[T any](v T) (T, error)

Clone creates a deep copy of a struct using JSON marshaling/unmarshaling. Note: This only works for JSON-serializable fields.

Example (StructCloning)

Example: Struct Cloning

package main

import (
	"fmt"
	"time"

	"github.com/alextanhongpin/core/types/structs"
)

// Example types for testing
type User struct {
	ID        int       `json:"id" db:"user_id" validate:"required"`
	Name      string    `json:"name" db:"full_name" validate:"required,min=2"`
	Email     string    `json:"email" db:"email_address" validate:"required,email"`
	Age       int       `json:"age" db:"age" validate:"min=0,max=150"`
	Active    bool      `json:"active" db:"is_active"`
	Profile   *Profile  `json:"profile,omitempty"`
	Metadata  Metadata  `json:"metadata"`
	Tags      []string  `json:"tags"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
}

type Profile struct {
	Bio      string `json:"bio"`
	Website  string `json:"website"`
	Location string `json:"location"`
	Avatar   string `json:"avatar"`
}

type Metadata map[string]any

func main() {
	original := User{
		ID:    1,
		Name:  "Alice",
		Email: "[email protected]",
		Profile: &Profile{
			Bio: "Original bio",
		},
		Tags: []string{"admin"},
	}

	// Clone the struct
	cloned, err := structs.Clone(original)
	if err != nil {
		fmt.Printf("Clone error: %v\n", err)
		return
	}

	// Modify the original
	original.Name = "Bob"
	original.Tags[0] = "user"

	fmt.Printf("Original name: %s\n", original.Name)
	fmt.Printf("Cloned name: %s\n", cloned.Name)
	fmt.Printf("Original tag: %s\n", original.Tags[0])
	fmt.Printf("Cloned tag: %s\n", cloned.Tags[0])

	// Note: Pointer fields are deeply cloned too
	if cloned.Profile != nil {
		fmt.Printf("Cloned profile bio: %s\n", cloned.Profile.Bio)
	}

}
Output:

Original name: Bob
Cloned name: Alice
Original tag: user
Cloned tag: admin
Cloned profile bio: Original bio

func GetFieldNames

func GetFieldNames(v any) ([]string, error)

GetFieldNames returns all field names in a struct.

func GetFieldValue

func GetFieldValue(v any, fieldName string) (any, error)

GetFieldValue returns the value of a field by name.

Example (DynamicProcessing)

Example: Dynamic Field Processing

package main

import (
	"fmt"
	"time"

	"github.com/alextanhongpin/core/types/structs"
)

// Example types for testing
type User struct {
	ID        int       `json:"id" db:"user_id" validate:"required"`
	Name      string    `json:"name" db:"full_name" validate:"required,min=2"`
	Email     string    `json:"email" db:"email_address" validate:"required,email"`
	Age       int       `json:"age" db:"age" validate:"min=0,max=150"`
	Active    bool      `json:"active" db:"is_active"`
	Profile   *Profile  `json:"profile,omitempty"`
	Metadata  Metadata  `json:"metadata"`
	Tags      []string  `json:"tags"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
}

type Profile struct {
	Bio      string `json:"bio"`
	Website  string `json:"website"`
	Location string `json:"location"`
	Avatar   string `json:"avatar"`
}

type Metadata map[string]any

func main() {
	user := User{
		ID:    1,
		Name:  "Alice",
		Email: "[email protected]",
		Age:   30,
	}

	// Dynamic field processing based on field names
	fieldNames, _ := structs.GetFieldNames(user)

	fmt.Println("Processing user fields:")
	for _, fieldName := range fieldNames {
		value, err := structs.GetFieldValue(user, fieldName)
		if err != nil {
			continue
		}

		// Process different field types
		switch fieldName {
		case "ID":
			if id, ok := value.(int); ok && id > 0 {
				fmt.Printf("  Valid ID: %d\n", id)
			}
		case "Email":
			if email, ok := value.(string); ok && email != "" {
				fmt.Printf("  Valid email: %s\n", email)
			}
		case "Age":
			if age, ok := value.(int); ok && age >= 18 {
				fmt.Printf("  Adult user: %d years old\n", age)
			}
		}
	}

}
Output:

Processing user fields:
  Valid ID: 1
  Valid email: [email protected]
  Adult user: 30 years old

func GetFields

func GetFields(v any) (map[string]any, error)

GetFields returns a map of field names to their values for a struct. Only works with struct types.

Example (ApiProcessing)

Example: API Response Processing

package main

import (
	"fmt"

	"github.com/alextanhongpin/core/types/structs"
)

func main() {
	type APIResponse struct {
		Success bool           `json:"success"`
		Data    map[string]any `json:"data"`
		Error   string         `json:"error,omitempty"`
		Meta    struct {
			Page  int `json:"page"`
			Total int `json:"total"`
		} `json:"meta"`
	}

	response := APIResponse{
		Success: true,
		Data: map[string]any{
			"users": []map[string]any{
				{"id": 1, "name": "Alice"},
				{"id": 2, "name": "Bob"},
			},
		},
	}
	response.Meta.Page = 1
	response.Meta.Total = 2

	// Analyze response structure
	fields, err := structs.GetFields(response)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Response type: %s\n", structs.Name(response))
	fmt.Printf("Has success field: %v\n", structs.HasField(response, "Success"))
	fmt.Printf("Has error field: %v\n", structs.HasField(response, "Error"))

	// Check if response indicates success
	if success, ok := fields["Success"].(bool); ok && success {
		fmt.Println("API call was successful")
	}

}
Output:

Response type: APIResponse
Has success field: true
Has error field: true
API call was successful
Example (FieldAnalysis)

Example: Field Analysis

package main

import (
	"fmt"
	"time"

	"github.com/alextanhongpin/core/types/structs"
)

// Example types for testing
type User struct {
	ID        int       `json:"id" db:"user_id" validate:"required"`
	Name      string    `json:"name" db:"full_name" validate:"required,min=2"`
	Email     string    `json:"email" db:"email_address" validate:"required,email"`
	Age       int       `json:"age" db:"age" validate:"min=0,max=150"`
	Active    bool      `json:"active" db:"is_active"`
	Profile   *Profile  `json:"profile,omitempty"`
	Metadata  Metadata  `json:"metadata"`
	Tags      []string  `json:"tags"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
}

type Profile struct {
	Bio      string `json:"bio"`
	Website  string `json:"website"`
	Location string `json:"location"`
	Avatar   string `json:"avatar"`
}

type Metadata map[string]any

func main() {
	user := User{
		ID:     1,
		Name:   "Alice",
		Email:  "[email protected]",
		Age:    30,
		Active: true,
		Tags:   []string{"admin", "premium"},
	}

	// Get all fields
	fields, err := structs.GetFields(user)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("User has %d fields\n", len(fields))

	// Get field names
	names, _ := structs.GetFieldNames(user)
	fmt.Printf("Field names: %v\n", names)

	// Check specific fields
	fmt.Printf("Has Name field: %v\n", structs.HasField(user, "Name"))
	fmt.Printf("Has Password field: %v\n", structs.HasField(user, "Password"))

	// Get specific field value
	if email, err := structs.GetFieldValue(user, "Email"); err == nil {
		fmt.Printf("Email: %s\n", email)
		if fields["Email"] == email {
			fmt.Println("Email field matches the value in fields map")
		} else {
			fmt.Println("Email field does not match the value in fields map")
		}
	} else {
		fmt.Println(err)
	}

}
Output:

User has 9 fields
Field names: [ID Name Email Age Active Profile Metadata Tags CreatedAt]
Has Name field: true
Has Password field: false
Email: [email protected]
Email field matches the value in fields map

func GetMethodNames

func GetMethodNames(v any) ([]string, error)

GetMethodNames returns all exported method names for a struct or pointer to struct.

func GetTags

func GetTags(v any, tagKey string) (map[string]string, error)

GetTags returns all struct tags for a given tag key.

Example (TagAnalysis)

Example: Struct Tag Analysis

package main

import (
	"fmt"
	"maps"
	"slices"
	"time"

	"github.com/alextanhongpin/core/types/structs"
)

// Example types for testing
type User struct {
	ID        int       `json:"id" db:"user_id" validate:"required"`
	Name      string    `json:"name" db:"full_name" validate:"required,min=2"`
	Email     string    `json:"email" db:"email_address" validate:"required,email"`
	Age       int       `json:"age" db:"age" validate:"min=0,max=150"`
	Active    bool      `json:"active" db:"is_active"`
	Profile   *Profile  `json:"profile,omitempty"`
	Metadata  Metadata  `json:"metadata"`
	Tags      []string  `json:"tags"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
}

type Profile struct {
	Bio      string `json:"bio"`
	Website  string `json:"website"`
	Location string `json:"location"`
	Avatar   string `json:"avatar"`
}

type Metadata map[string]any

func main() {
	user := User{}

	// Get JSON tags
	jsonTags, err := structs.GetTags(user, "json")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("JSON tags:")
	fields := slices.Sorted(maps.Keys(jsonTags))
	for _, field := range fields {
		tag := jsonTags[field]
		fmt.Printf("  %s: %s\n", field, tag)
	}

	// Get validation tags
	validateTags, _ := structs.GetTags(user, "validate")
	fmt.Println("\nValidation tags:")

	fields = slices.Sorted(maps.Keys(validateTags))
	for _, field := range fields {
		tag := validateTags[field]
		fmt.Printf("  %s: %s\n", field, tag)
	}

}
Output:

JSON tags:
  Active: active
  Age: age
  CreatedAt: created_at
  Email: email
  ID: id
  Metadata: metadata
  Name: name
  Profile: profile,omitempty
  Tags: tags

Validation tags:
  Age: min=0,max=150
  Email: required,email
  ID: required
  Name: required,min=2

func HasField

func HasField(v any, fieldName string) bool

HasField checks if a struct has a field with the given name.

func IsNil

func IsNil(v any) bool

IsNil returns true if the value is nil or a nil pointer.

Example (TypeChecking)

Example: Type Checking and Nil Handling

package main

import (
	"fmt"
	"time"

	"github.com/alextanhongpin/core/types/structs"
)

// Example types for testing
type User struct {
	ID        int       `json:"id" db:"user_id" validate:"required"`
	Name      string    `json:"name" db:"full_name" validate:"required,min=2"`
	Email     string    `json:"email" db:"email_address" validate:"required,email"`
	Age       int       `json:"age" db:"age" validate:"min=0,max=150"`
	Active    bool      `json:"active" db:"is_active"`
	Profile   *Profile  `json:"profile,omitempty"`
	Metadata  Metadata  `json:"metadata"`
	Tags      []string  `json:"tags"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
}

type Profile struct {
	Bio      string `json:"bio"`
	Website  string `json:"website"`
	Location string `json:"location"`
	Avatar   string `json:"avatar"`
}

type Metadata map[string]any

func main() {
	var user *User
	var iface any
	var slice []string
	var m map[string]int

	fmt.Printf("Nil pointer: %v\n", structs.IsNil(user))
	fmt.Printf("Nil interface: %v\n", structs.IsNil(iface))
	fmt.Printf("Nil slice: %v\n", structs.IsNil(slice))
	fmt.Printf("Nil map: %v\n", structs.IsNil(m))

	// Non-nil values
	user = &User{}
	slice = make([]string, 0)
	m = make(map[string]int)

	fmt.Printf("Empty struct pointer: %v\n", structs.IsNil(user))
	fmt.Printf("Empty slice: %v\n", structs.IsNil(slice))
	fmt.Printf("Empty map: %v\n", structs.IsNil(m))

}
Output:

Nil pointer: true
Nil interface: true
Nil slice: true
Nil map: true
Empty struct pointer: false
Empty slice: false
Empty map: false

func IsPointer

func IsPointer(v any) bool

IsPointer returns true if the value is a pointer.

func IsStruct

func IsStruct(v any) bool

IsStruct returns true if the value is a struct or pointer to struct.

func Kind

func Kind(v any) reflect.Kind

Kind returns the underlying kind of the value.

func Name

func Name(v any) string

Name returns the type name without the package prefix. For "package.TypeName", returns just "TypeName".

func NonZero

func NonZero(v any) error

NonZero validates that all fields in a struct are non-zero values. It recursively checks nested structs, slices, arrays, and maps using reflection. Returns a FieldError indicating the first empty field found.

Example (BuilderPattern)

Example: Struct Builder Pattern with Validation

package main

import (
	"fmt"
	"time"

	"github.com/alextanhongpin/core/types/structs"
)

// Example types for testing
type User struct {
	ID        int       `json:"id" db:"user_id" validate:"required"`
	Name      string    `json:"name" db:"full_name" validate:"required,min=2"`
	Email     string    `json:"email" db:"email_address" validate:"required,email"`
	Age       int       `json:"age" db:"age" validate:"min=0,max=150"`
	Active    bool      `json:"active" db:"is_active"`
	Profile   *Profile  `json:"profile,omitempty"`
	Metadata  Metadata  `json:"metadata"`
	Tags      []string  `json:"tags"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
}

type Profile struct {
	Bio      string `json:"bio"`
	Website  string `json:"website"`
	Location string `json:"location"`
	Avatar   string `json:"avatar"`
}

type Metadata map[string]any

func main() {
	type UserBuilder struct {
		user User
	}

	newUserBuilder := func() *UserBuilder {
		return &UserBuilder{user: User{}}
	}

	withID := func(ub *UserBuilder, id int) *UserBuilder {
		ub.user.ID = id
		return ub
	}

	withName := func(ub *UserBuilder, name string) *UserBuilder {
		ub.user.Name = name
		return ub
	}

	withEmail := func(ub *UserBuilder, email string) *UserBuilder {
		ub.user.Email = email
		return ub
	}

	withTags := func(ub *UserBuilder, tags []string) *UserBuilder {
		ub.user.Tags = tags
		return ub
	}

	build := func(ub *UserBuilder) (User, error) {
		// Validate required fields before building
		if err := structs.NonZero(ub.user); err != nil {
			return User{}, fmt.Errorf("user validation failed: %w", err)
		}
		return ub.user, nil
	}

	// Build a valid user
	builder := newUserBuilder()
	builder.user.Age = 25                             // Optional field, can be set later
	builder.user.Active = true                        // Optional field, can be set later
	builder.user.Metadata = Metadata{"role": "admin"} // Optional field, can be set later

	builder = withID(builder, 1)
	builder = withName(builder, "Alice")
	builder = withEmail(builder, "[email protected]")
	builder = withTags(builder, []string{"admin", "premium"})

	user, err := build(builder)
	if err != nil {
		fmt.Printf("Build failed: %v\n", err)
	} else {
		fmt.Printf("Built user: %s (%s)\n", user.Name, user.Email)
	}

	// Try to build an invalid user
	invalidBuilder := newUserBuilder()
	invalidBuilder = withID(invalidBuilder, 1)
	invalidBuilder = withName(invalidBuilder, "Bob")
	// Missing email

	_, err = build(invalidBuilder)
	if err != nil {
		fmt.Printf("Validation caught missing field: %v\n", err)
	}

}
Output:

Build failed: user validation failed: field "Profile" is empty
Validation caught missing field: user validation failed: field "Email" is empty
Example (ConfigValidation)

Example: Configuration Validation

package main

import (
	"errors"
	"fmt"

	"github.com/alextanhongpin/core/types/structs"
)

func main() {
	type DatabaseConfig struct {
		Host     string `json:"host"`
		Port     int    `json:"port"`
		Username string `json:"username"`
		Password string `json:"password"`
		Database string `json:"database"`
		SSL      bool   `json:"ssl"`
	}

	type ServerConfig struct {
		Database DatabaseConfig `json:"database"`
		Redis    struct {
			Host string `json:"host"`
			Port int    `json:"port"`
		} `json:"redis"`
		Port int `json:"port"`
	}

	// Valid configuration
	validConfig := ServerConfig{
		Database: DatabaseConfig{
			Host:     "localhost",
			Port:     5432,
			Username: "admin",
			Password: "secret",
			Database: "myapp",
			SSL:      true,
		},
		Port: 8080,
	}

	// Add Redis config
	validConfig.Redis.Host = "localhost"
	validConfig.Redis.Port = 6379

	if err := structs.NonZero(validConfig); err != nil {
		fmt.Printf("Config validation failed: %v\n", err)
	} else {
		fmt.Println("Configuration is valid ✓")
	}

	// Invalid configuration (missing database password)
	invalidConfig := validConfig
	invalidConfig.Database.Password = ""

	if err := structs.NonZero(invalidConfig); err != nil {
		var fieldErr *structs.FieldError
		if errors.As(err, &fieldErr) {
			fmt.Printf("Missing required field: %s\n", fieldErr.Field)
		}
	}

}
Output:

Configuration is valid ✓
Missing required field: Password
Example (Validation)

Example: Struct Validation

package main

import (
	"errors"
	"fmt"
	"time"

	"github.com/alextanhongpin/core/types/structs"
)

// Example types for testing
type User struct {
	ID        int       `json:"id" db:"user_id" validate:"required"`
	Name      string    `json:"name" db:"full_name" validate:"required,min=2"`
	Email     string    `json:"email" db:"email_address" validate:"required,email"`
	Age       int       `json:"age" db:"age" validate:"min=0,max=150"`
	Active    bool      `json:"active" db:"is_active"`
	Profile   *Profile  `json:"profile,omitempty"`
	Metadata  Metadata  `json:"metadata"`
	Tags      []string  `json:"tags"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
}

type Profile struct {
	Bio      string `json:"bio"`
	Website  string `json:"website"`
	Location string `json:"location"`
	Avatar   string `json:"avatar"`
}

type Metadata map[string]any

func main() {
	// Valid user
	validUser := User{
		ID:     1,
		Name:   "Alice",
		Email:  "[email protected]",
		Age:    30,
		Active: true,
		Profile: &Profile{
			Bio:      "Software Engineer",
			Website:  "https://alice.dev",
			Avatar:   "https://alice.dev/avatar.png",
			Location: "Wonderland",
		},
		Metadata:  Metadata{"role": "admin"},
		Tags:      []string{"premium"},
		CreatedAt: time.Now(),
	}

	if err := structs.NonZero(validUser); err != nil {
		fmt.Printf("Valid user error: %v\n", err)
	} else {
		fmt.Println("Valid user: all fields non-zero ✓")
	}

	// Invalid user (missing name)
	invalidUser := User{
		ID:    1,
		Email: "[email protected]",
		// Name is missing (empty string)
	}

	if err := structs.NonZero(invalidUser); err != nil {
		var fieldErr *structs.FieldError
		if errors.As(err, &fieldErr) {
			fmt.Printf("Invalid user - Field: %s, Path: %s\n",
				fieldErr.Field, fieldErr.Path)
		}
	}

}
Output:

Valid user: all fields non-zero ✓
Invalid user - Field: Name, Path: structs_test.User.Name

func PkgName

func PkgName(v any) string

PkgName returns the package-qualified type name of the value. For structs, returns the full package.Type format. For other types, returns the kind name.

func Type

func Type(v any) string

Type returns the full type name of the value including package information. Returns "nil" for nil values.

Example (TypeIntrospection)

Example: Type Introspection

package main

import (
	"fmt"
	"time"

	"github.com/alextanhongpin/core/types/structs"
)

// Example types for testing
type User struct {
	ID        int       `json:"id" db:"user_id" validate:"required"`
	Name      string    `json:"name" db:"full_name" validate:"required,min=2"`
	Email     string    `json:"email" db:"email_address" validate:"required,email"`
	Age       int       `json:"age" db:"age" validate:"min=0,max=150"`
	Active    bool      `json:"active" db:"is_active"`
	Profile   *Profile  `json:"profile,omitempty"`
	Metadata  Metadata  `json:"metadata"`
	Tags      []string  `json:"tags"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
}

type Profile struct {
	Bio      string `json:"bio"`
	Website  string `json:"website"`
	Location string `json:"location"`
	Avatar   string `json:"avatar"`
}

type Metadata map[string]any

func main() {
	user := User{ID: 1, Name: "Alice"}
	userPtr := &user

	fmt.Printf("User type: %s\n", structs.Type(user))
	fmt.Printf("User pointer type: %s\n", structs.Type(userPtr))
	fmt.Printf("User package name: %s\n", structs.PkgName(user))
	fmt.Printf("User simple name: %s\n", structs.Name(user))
	fmt.Printf("User kind: %s\n", structs.Kind(user))
	fmt.Printf("Is struct: %v\n", structs.IsStruct(user))
	fmt.Printf("Is pointer: %v\n", structs.IsPointer(userPtr))

}
Output:

User type: structs_test.User
User pointer type: *structs_test.User
User package name: structs_test.User
User simple name: User
User kind: struct
Is struct: true
Is pointer: true

Types

type FieldError

type FieldError struct {
	Path  string
	Field string
}

FieldError represents an error for a specific field.

func (*FieldError) Error

func (fe *FieldError) Error() string

Error returns the error message.

func (*FieldError) String

func (fe *FieldError) String() string

String returns a string representation of the error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL