reflection

package module
v0.0.0-...-aea4ca2 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2025 License: MIT Imports: 4 Imported by: 4

README

go-reflection

Utilities extending Go's reflect package with practical helper functions for common reflection tasks.

Go Reference Go Report Card

Installation

go get github.com/ungerik/go-reflection

Features

  • Pointer Handling: Safe dereferencing and nil checking for any value type
  • Type Utilities: Common type constants and type dereferencing
  • Struct Field Manipulation: Flatten anonymous embedded fields, iterate, extract values
  • Validation: Validate struct fields with custom validation functions
  • Zero Value Detection: Check for zero values in structs and fields
  • Value Conversion: Convert reflect.Value slices to interface{} slices

Table of Contents

Quick Start

package main

import (
    "fmt"
    "reflect"
    "github.com/ungerik/go-reflection"
)

type Address struct {
    Street string
    City   string
}

type Person struct {
    Name    string `json:"name"`
    Age     int    `json:"age"`
    Email   string `json:"email"`
    Address        // Anonymous embedded field
}

func main() {
    person := Person{
        Name:  "Alice",
        Age:   30,
        Email: "[email protected]",
        Address: Address{
            Street: "123 Main St",
            City:   "Springfield",
        },
    }

    // Get flattened field names (including embedded struct fields)
    fieldNames := reflection.FlatStructFieldNames(reflect.TypeOf(person))
    fmt.Println("Fields:", fieldNames)
    // Output: Fields: [Name Age Email Street City]

    // Get field names from struct tags
    tagNames := reflection.FlatStructFieldTagsOrNames(reflect.TypeOf(person), "json")
    fmt.Println("JSON tags:", tagNames)
    // Output: JSON tags: [name age email Street City]

    // Safe nil checking
    var ptr *Person
    fmt.Println("Is nil:", reflection.IsNil(reflect.ValueOf(ptr)))
    // Output: Is nil: true
}

Pointer Utilities

ValueOf

ValueOf is an enhanced version of reflect.ValueOf that handles reflect.Value arguments:

// If val is already a reflect.Value, return it as-is
// Otherwise, return reflect.ValueOf(val)
v := reflection.ValueOf(someValue)
DerefValue

Dereference a value until a non-pointer type or nil is reached:

var x = 42
ptr := &x
ptrPtr := &ptr

// Dereferences all pointer levels
v := reflection.DerefValue(ptrPtr)
fmt.Println(v.Int()) // 42
DerefType

Dereference a type until a non-pointer type is reached:

t := reflect.TypeOf((**int)(nil))
dereferenced := reflection.DerefType(t)
fmt.Println(dereferenced.Kind()) // int
IsNil

Safe nil checking for any value type:

// Unlike reflect.Value.IsNil(), this is safe for any value type
var ptr *int
fmt.Println(reflection.IsNil(reflect.ValueOf(ptr))) // true

var num int
fmt.Println(reflection.IsNil(reflect.ValueOf(num))) // false

// Safe for invalid values too
var v reflect.Value
fmt.Println(reflection.IsNil(v)) // true

Type Constants

Pre-defined type constants for common interfaces:

// Type of error interface
errorType := reflection.TypeOfError

// Type of empty interface{}
anyType := reflection.TypeOfEmptyInterface

// Use in type comparisons
func returnsError(t reflect.Type) bool {
    return t.NumOut() > 0 && t.Out(0) == reflection.TypeOfError
}

Struct Field Operations

Flattening Anonymous Fields

Work with embedded (anonymous) struct fields as if they were top-level fields:

type Base struct {
    ID   int
    Name string
}

type Extended struct {
    Base  // Anonymous field
    Email string
}

// Get count of all fields (including embedded)
count := reflection.FlatStructFieldCount(reflect.TypeOf(Extended{}))
fmt.Println(count) // 3 (ID, Name, Email)

// Get all field names
names := reflection.FlatStructFieldNames(reflect.TypeOf(Extended{}))
fmt.Println(names) // [ID Name Email]
Field Iteration

Multiple ways to iterate over struct fields:

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

user := User{Name: "Bob", Email: "[email protected]"}

// Method 1: Get slice of field info
fields := reflection.FlatExportedStructFields(user)
for _, field := range fields {
    fmt.Printf("%s = %v\n", field.Field.Name, field.Value.Interface())
}

// Method 2: Use callback
reflection.EnumFlatExportedStructFields(user, func(field reflect.StructField, value reflect.Value) {
    fmt.Printf("%s = %v\n", field.Name, value.Interface())
})

// Method 3: Use iterator (Go 1.23+)
for field, value := range reflection.FlatExportedStructFieldsIter(user) {
    fmt.Printf("%s = %v\n", field.Name, value.Interface())
}
Field Names and Tags

Extract field names from struct tags:

type Product struct {
    ID    int    `json:"id" db:"product_id"`
    Name  string `json:"name" db:"product_name"`
    Price float64 `json:"price" db:"product_price"`
}

// Get json tags
jsonTags := reflection.FlatStructFieldTags(reflect.TypeOf(Product{}), "json")
fmt.Println(jsonTags) // [id name price]

// Get db tags
dbTags := reflection.FlatStructFieldTags(reflect.TypeOf(Product{}), "db")
fmt.Println(dbTags) // [product_id product_name product_price]

// Get tags or field names as fallback
tagsOrNames := reflection.FlatStructFieldTagsOrNames(reflect.TypeOf(Product{}), "xml")
fmt.Println(tagsOrNames) // [ID Name Price] (uses field names since no xml tags)
Field Values

Extract field values as interfaces:

type Point struct {
    X int
    Y int
}

p := Point{X: 10, Y: 20}

// Get field values
values := reflection.FlatStructFieldValues(reflect.ValueOf(p))

// Convert to interface{} slice
interfaces := reflection.ValuesToInterfaces(values...)
fmt.Println(interfaces) // [10 20]
Field Names with Values

Get structured information about fields:

type Config struct {
    Host string `json:"host"`
    Port int    `json:"port"`
}

config := Config{Host: "localhost", Port: 8080}

// Get slice of field info with names
fields := reflection.FlatExportedStructFieldValueNames(config, "json")
for _, field := range fields {
    fmt.Printf("%s: %v\n", field.Name, field.Value.Interface())
}
// Output:
// host: localhost
// port: 8080

// Or get as map
fieldMap := reflection.FlatExportedStructFieldValueNameMap(config, "json")
hostField := fieldMap["host"]
fmt.Println(hostField.Value.String()) // localhost

Validation

Validate struct fields using custom validation functions:

import (
    "errors"
    "strings"
)

type UserInput struct {
    Username string `json:"username"`
    Email    string `json:"email"`
    Age      int    `json:"age"`
}

// Custom validation function
func validateField(val any) error {
    switch v := val.(type) {
    case string:
        if strings.TrimSpace(v) == "" {
            return errors.New("cannot be empty")
        }
    case int:
        if v < 0 {
            return errors.New("must be positive")
        }
    }
    return nil
}

func main() {
    input := UserInput{
        Username: "",        // Invalid: empty
        Email:    "[email protected]", // Valid
        Age:      -5,        // Invalid: negative
    }

    // Validate all fields
    fieldErrors := reflection.ValidateStructFields(validateField, input, "", "json")

    for _, ferr := range fieldErrors {
        fmt.Printf("Field %s: %v\n", ferr.FieldName, ferr.FieldError)
    }
    // Output:
    // Field username: cannot be empty
    // Field age: must be positive
}

The validation function is called with:

  1. The field value
  2. The field value's address (if addressable)
  3. The dereferenced value (if pointer and not nil)

This allows validation functions to work with values, pointers, and implement interface-based validation.

Zero Value Detection

Check for zero (default) values in structs:

type Form struct {
    Name     string   `json:"name"`
    Email    string   `json:"email"`
    Age      int      `json:"age"`
    Tags     []string `json:"tags"`
    Optional *string  `json:"optional"`
}

func main() {
    form := Form{
        Name:  "John",
        // Email is zero value (empty string)
        Age:   0,  // zero value
        Tags:  nil, // zero value (nil slice)
        // Optional is nil
    }

    // Check if entire value is zero
    fmt.Println(reflection.IsZero(0))        // true
    fmt.Println(reflection.IsZero(""))       // true
    fmt.Println(reflection.IsZero("hello"))  // false

    // Find zero-value fields in struct
    zeroFields := reflection.ZeroValueExportedStructFieldNames(form, "", "json")
    fmt.Println("Zero fields:", zeroFields)
    // Output: Zero fields: [email age tags optional]
}
Advanced Zero Value Detection
type Nested struct {
    Inner struct {
        Value string `json:"value"`
    } `json:"inner"`
    Items []int `json:"items"`
}

nested := Nested{
    Items: []int{1, 0, 3}, // Has a zero element at index 1
}

zeroFields := reflection.ZeroValueExportedStructFieldNames(nested, "", "json")
fmt.Println(zeroFields)
// Output: [inner.value items[1]]
// Note: Shows nested field and array index with zero value

Value Conversion

Convert reflect.Value slices to interface{} slices:

values := []reflect.Value{
    reflect.ValueOf(42),
    reflect.ValueOf("hello"),
    reflect.ValueOf(true),
}

// Convert to []interface{}
interfaces := reflection.ValuesToInterfaces(values...)
fmt.Printf("%#v\n", interfaces)
// Output: []interface {}{42, "hello", true}

Advanced Examples

Custom Struct Mapper

Map struct fields to a map using struct tags:

type User struct {
    ID       int    `db:"user_id"`
    Username string `db:"username"`
    Email    string `db:"email"`
}

func structToMap(s any, tagKey string) map[string]any {
    fields := reflection.FlatExportedStructFieldValueNames(s, tagKey)
    result := make(map[string]any, len(fields))
    for _, field := range fields {
        result[field.Name] = field.Value.Interface()
    }
    return result
}

user := User{ID: 1, Username: "alice", Email: "[email protected]"}
dbMap := structToMap(user, "db")
fmt.Println(dbMap)
// Output: map[email:[email protected] user_id:1 username:alice]
Validation with Zero Value Check

Combine validation and zero value detection:

type CreateUserRequest struct {
    Username string `json:"username" required:"true"`
    Email    string `json:"email" required:"true"`
    Age      *int   `json:"age"`  // Optional
}

func validateRequired(req any) []string {
    var missing []string

    fields := reflection.FlatExportedStructFieldValueNames(req, "json")
    for _, field := range fields {
        // Check if field has required tag
        if field.Field.Tag.Get("required") == "true" {
            if reflection.IsNil(field.Value) || reflection.IsZero(field.Value.Interface()) {
                missing = append(missing, field.Name)
            }
        }
    }

    return missing
}

req := CreateUserRequest{
    Username: "", // Missing required field
    // Email missing
    Age: nil, // Optional, so OK
}

missing := validateRequired(req)
fmt.Println("Missing required fields:", missing)
// Output: Missing required fields: [username email]
Dynamic Field Filtering

Filter struct fields based on criteria:

type Product struct {
    ID          int     `json:"id"`
    Name        string  `json:"name"`
    Price       float64 `json:"price"`
    InternalRef string  `json:"-"` // Excluded by json tag
}

func getJSONFields(s any) []string {
    var fieldNames []string

    reflection.EnumFlatExportedStructFields(s, func(field reflect.StructField, value reflect.Value) {
        jsonTag := field.Tag.Get("json")
        if jsonTag != "" && jsonTag != "-" {
            // Extract field name from tag (before comma)
            if comma := strings.IndexByte(jsonTag, ','); comma != -1 {
                jsonTag = jsonTag[:comma]
            }
            fieldNames = append(fieldNames, jsonTag)
        }
    })

    return fieldNames
}

product := Product{ID: 1, Name: "Widget", Price: 9.99, InternalRef: "WID-001"}
jsonFields := getJSONFields(product)
fmt.Println(jsonFields) // [id name price]

Best Practices

1. Prefer Iteration Methods Based on Use Case
// ✅ Good: Use iterator for large structs (memory efficient)
for field, value := range reflection.FlatExportedStructFieldsIter(largeStruct) {
    process(field, value)
}

// ✅ Good: Use slice when you need to modify or reorder
fields := reflection.FlatExportedStructFields(s)
sort.Slice(fields, func(i, j int) bool {
    return fields[i].Field.Name < fields[j].Field.Name
})

// ✅ Good: Use callback when you just need to process each field
reflection.EnumFlatExportedStructFields(s, processField)
2. Handle Nil Safely
// ✅ Good: Use IsNil for safe nil checking
if reflection.IsNil(v) {
    // Handle nil case
}

// ❌ Avoid: Direct IsNil() call can panic on non-nillable types
if v.IsNil() { // Panics if v is an int, string, etc.
    // ...
}
3. Cache Type Information
// ✅ Good: Cache type information for repeated operations
type StructInfo struct {
    fieldNames []string
    fieldTags  map[string]string
}

func getStructInfo(t reflect.Type) StructInfo {
    // Cache this result
    return StructInfo{
        fieldNames: reflection.FlatStructFieldNames(t),
        fieldTags:  makeTagMap(t),
    }
}
4. Use Struct Tags for Metadata
// ✅ Good: Use struct tags to drive reflection behavior
type Model struct {
    ID   int    `db:"id" validate:"required"`
    Name string `db:"name" validate:"required,min=3"`
}

// Then use tags in your reflection code
fields := reflection.FlatExportedStructFieldValueNames(model, "db")

API Reference

See pkg.go.dev for complete API documentation.

Core Functions
  • ValueOf(any) reflect.Value - Enhanced ValueOf that handles reflect.Value arguments
  • DerefValue(any) reflect.Value - Dereference until non-pointer or nil
  • DerefType(reflect.Type) reflect.Type - Dereference type until non-pointer
  • IsNil(reflect.Value) bool - Safe nil checking for any value type
  • IsZero(any) bool - Check if value is zero value
Struct Field Functions
  • FlatStructFieldCount(reflect.Type) int - Count of flattened fields
  • FlatStructFieldNames(reflect.Type) []string - Names of flattened fields
  • FlatStructFieldTags(reflect.Type, string) []string - Tag values of flattened fields
  • FlatStructFieldTagsOrNames(reflect.Type, string) []string - Tags or names as fallback
  • FlatStructFieldValues(reflect.Value) []reflect.Value - Values of flattened fields
  • FlatExportedStructFields(any) []StructFieldValue - Field info with values
  • FlatExportedStructFieldsIter(any) iter.Seq2[...] - Iterator over fields (Go 1.23+)
  • FlatExportedStructFieldValueNames(any, string) []StructFieldValueName - Fields with tag names
  • FlatExportedStructFieldValueNameMap(any, string) map[string]StructFieldValueName - Field map by name
Validation Functions
  • ValidateStructFields(func(any) error, any, string, string, ...string) []FieldError - Validate fields
  • ZeroValueExportedStructFieldNames(any, string, string, ...string) []string - Find zero-value fields
Utility Functions
  • ValuesToInterfaces(...reflect.Value) []any - Convert Values to interfaces

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

See LICENSE file for details.

Documentation

Overview

Package reflection provides utilities that extend Go's reflect package with practical helper functions for common reflection tasks.

The package offers:

  • Safe pointer dereferencing and nil checking
  • Struct field manipulation with support for anonymous embedded fields
  • Field validation with custom validation functions
  • Zero value detection for structs and fields
  • Value conversion utilities

Most functions that work with structs support flattening of anonymous embedded fields, making them appear as top-level fields. This is useful for ORM-like operations, serialization, and validation.

Example usage:

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

user := User{Name: "Alice", Email: "[email protected]"}

// Get field names from struct tags
names := reflection.FlatStructFieldTagsOrNames(reflect.TypeOf(user), "json")
// names: ["name", "email"]

// Iterate over fields
for field, value := range reflection.FlatExportedStructFieldsIter(user) {
    fmt.Printf("%s = %v\n", field.Name, value.Interface())
}

Index

Constants

This section is empty.

Variables

View Source
var (
	// TypeOfError is the reflect.Type of the error interface.
	// Useful for type comparisons when working with functions that return errors.
	//
	// Example:
	//
	//	func returnsError(fn reflect.Type) bool {
	//	    return fn.NumOut() > 0 && fn.Out(0) == reflection.TypeOfError
	//	}
	TypeOfError = reflect.TypeOf((*error)(nil)).Elem()

	// TypeOfEmptyInterface is the reflect.Type of the empty interface (any).
	// Useful for type comparisons when working with generic interfaces.
	TypeOfEmptyInterface = reflect.TypeOf((*interface{})(nil)).Elem()
)

Functions

func DerefType

func DerefType(t reflect.Type) reflect.Type

DerefType dereferences a type until a non-pointer type is found. It repeatedly follows pointer types until reaching a concrete type.

Example:

t := reflect.TypeOf((**int)(nil))
concrete := reflection.DerefType(t)
fmt.Println(concrete.Kind()) // int

func DerefValue

func DerefValue(val any) reflect.Value

DerefValue dereferences val until a non-pointer type or nil pointer is found. It repeatedly follows pointers until reaching a concrete value or a nil pointer.

The argument val can be any value or a reflect.Value (handled by ValueOf).

Example:

x := 42
ptr := &x
ptrPtr := &ptr

v := reflection.DerefValue(ptrPtr)
fmt.Println(v.Int()) // 42

var nilPtr *int
v2 := reflection.DerefValue(nilPtr)
fmt.Println(v2.IsValid()) // false (nil pointer)

func DerefValueAndType

func DerefValueAndType(val any) (reflect.Value, reflect.Type)

DerefValueAndType dereferences val until a non-pointer value is found and returns both the dereferenced value and its type.

This is a convenience function that combines DerefValue with getting the type.

Example:

x := 42
ptr := &x
value, typ := reflection.DerefValueAndType(ptr)
fmt.Println(value.Int(), typ.Kind()) // 42 int

func EnumFlatExportedStructFields

func EnumFlatExportedStructFields(val any, callback func(reflect.StructField, reflect.Value))

EnumFlatExportedStructFields returns reflect.StructField and reflect.Value of flattened struct fields, meaning that the fields of anonoymous embedded fields are flattened to the top level of the struct. The argument val can be a struct, a pointer to a struct, or a reflect.Value.

func FlatExportedStructFieldValueNameMap

func FlatExportedStructFieldValueNameMap(val any, nameTag string) map[string]StructFieldValueName

FlatExportedStructFieldValueNameMap returns a slice of StructFieldValueName of flattened struct fields, meaning that the fields of anonoymous embedded fields are flattened to the top level of the struct. The argument val can be a struct, a pointer to a struct, or a reflect.Value.

func FlatExportedStructFieldsIter

func FlatExportedStructFieldsIter(s any) iter.Seq2[reflect.StructField, reflect.Value]

FlatExportedStructFieldsIter returns an iterator over flattened exported struct fields. Anonymous embedded fields are flattened to the top level.

This is the most memory-efficient way to iterate over struct fields, as it doesn't allocate a slice. Requires Go 1.23+.

The argument s can be a struct, a pointer to a struct, or a reflect.Value.

Example:

type User struct {
    Name  string
    Email string
}
user := User{Name: "Bob", Email: "[email protected]"}
for field, value := range reflection.FlatExportedStructFieldsIter(user) {
    fmt.Printf("%s = %v\n", field.Name, value.Interface())
}
// Output:
// Name = Bob
// Email = [email protected]

func FlatStructFieldCount

func FlatStructFieldCount(t reflect.Type) int

FlatStructFieldCount returns the number of flattened struct fields. Anonymous embedded fields are flattened, meaning their fields are counted as top-level fields of the struct.

Example:

type Base struct {
    ID   int
    Name string
}
type Extended struct {
    Base  // Anonymous field - will be flattened
    Email string
}
count := reflection.FlatStructFieldCount(reflect.TypeOf(Extended{}))
fmt.Println(count) // 3 (ID, Name, Email)

func FlatStructFieldNames

func FlatStructFieldNames(t reflect.Type) (names []string)

FlatStructFieldNames returns the names of flattened struct fields. Anonymous embedded fields are flattened, meaning their field names appear at the top level alongside non-embedded fields.

Example:

type Address struct {
    Street string
    City   string
}
type Person struct {
    Name    string
    Address // Anonymous field
}
names := reflection.FlatStructFieldNames(reflect.TypeOf(Person{}))
fmt.Println(names) // [Name Street City]

func FlatStructFieldTags

func FlatStructFieldTags(t reflect.Type, tagKey string) (tagValues []string)

FlatStructFieldTags returns the tag values for a tagKey of flattened struct fields, meaning that the fields of anonoymous embedded fields are flattened to the top level of the struct. An empty string is returned for fields that don't have a matching tag.

func FlatStructFieldTagsOrNames

func FlatStructFieldTagsOrNames(t reflect.Type, tagKey string) (tagsOrNames []string)

FlatStructFieldTagsOrNames returns the tag values for tagKey or the names of the field if no tag with tagKey is defined at a struct field. Fields are flattened, meaning that the fields of anonoymous embedded fields are flattened to the top level of the struct.

func FlatStructFieldValues

func FlatStructFieldValues(v reflect.Value) (values []reflect.Value)

FlatStructFieldValues returns the values of flattened struct fields, meaning that the fields of anonoymous embedded fields are flattened to the top level of the struct.

func IsNil

func IsNil(v reflect.Value) bool

IsNil returns true if v is of a nillable type and is nil.

Unlike reflect.Value.IsNil(), this function is safe to call for any value and type. It will not panic for non-nillable types (like int, string, etc.).

Returns true for:

  • Invalid/zero reflect.Value (result of reflect.ValueOf(nil))
  • Nil pointers, interfaces, channels, functions, maps, or slices

Returns false for:

  • Non-nillable types (int, string, struct, etc.)
  • Non-nil values of nillable types

Example:

var ptr *int
fmt.Println(reflection.IsNil(reflect.ValueOf(ptr))) // true

var num int
fmt.Println(reflection.IsNil(reflect.ValueOf(num))) // false (non-nillable type)

var v reflect.Value
fmt.Println(reflection.IsNil(v)) // true (invalid/zero value)

func IsZero

func IsZero(v any) bool

IsZero returns true if the underlying value of v is the zero (default) value of its type, or if v itself is nil.

This uses reflect.DeepEqual to compare the value with its zero value, so it works correctly for structs, slices, maps, and other composite types.

Example:

fmt.Println(reflection.IsZero(0))        // true
fmt.Println(reflection.IsZero(""))       // true
fmt.Println(reflection.IsZero(nil))      // true
fmt.Println(reflection.IsZero("hello"))  // false
fmt.Println(reflection.IsZero([]int{}))  // true (empty slice is zero)

func ValueOf

func ValueOf(val any) reflect.Value

ValueOf is an enhanced version of reflect.ValueOf that handles reflect.Value arguments. If val is already a reflect.Value, it returns val as-is without wrapping. Otherwise, it returns reflect.ValueOf(val).

This is useful when writing generic reflection code that might receive either regular values or already-reflected values.

Example:

v := reflect.ValueOf(42)
result := reflection.ValueOf(v)
// result is the same as v, not reflect.ValueOf(v)

result2 := reflection.ValueOf(42)
// result2 is reflect.ValueOf(42)

func ValuesToInterfaces

func ValuesToInterfaces(values ...reflect.Value) []any

ValuesToInterfaces converts a slice of reflect.Value to a slice of any (interface{}) by calling reflect.Value.Interface() for each value.

This is useful when you have reflect.Value objects and need to pass them to functions that accept any/interface{} parameters.

Example:

values := []reflect.Value{
    reflect.ValueOf(42),
    reflect.ValueOf("hello"),
    reflect.ValueOf(true),
}
interfaces := reflection.ValuesToInterfaces(values...)
// interfaces: []any{42, "hello", true}

func ZeroValueExportedStructFieldNames

func ZeroValueExportedStructFieldNames(st any, namePrefix, nameTag string, namesToValidate ...string) (zeroNames []string)

ZeroValueExportedStructFieldNames returns the names of exported struct fields that have zero (default) values.

Parameters:

  • st: The struct value to examine (can be a struct, pointer to struct, or reflect.Value)
  • namePrefix: A prefix to add to all returned field names
  • nameTag: The struct tag key to use for field names (e.g., "json"). If empty or not found, uses Go field name
  • namesToValidate: Optional list of specific field names to check. If empty, checks all fields

Behavior:

  • Anonymous embedded structs are flattened
  • Named sub-structs are checked recursively with their name as prefix (e.g., "Address.Street")
  • Zero elements in arrays/slices are reported with index notation (e.g., "Items[1]")
  • Struct tag values can include comma-separated options; only the part before the comma is used
  • Fields with tag value "-" are ignored

Example:

type Form struct {
    Name  string   `json:"name"`
    Email string   `json:"email"`
    Age   int      `json:"age"`
    Tags  []string `json:"tags"`
}

form := Form{Name: "John", Tags: []string{"a", "", "c"}}
zeros := reflection.ZeroValueExportedStructFieldNames(form, "", "json")
// zeros: ["email", "age", "tags[1]"]

Types

type FieldError

type FieldError struct {
	FieldName  string // Name of the field that failed validation
	FieldError error  // The validation error for this field
}

FieldError represents a validation error for a specific struct field. It combines the field name with its validation error.

func ValidateStructFields

func ValidateStructFields(validateFunc func(any) error, st any, namePrefix, nameTag string, namesToValidate ...string) (fieldErrors []FieldError)

ValidateStructFields validates all exported fields of a struct using a custom validation function.

The validation function is called three times for each field (if applicable):

  1. With the field value itself
  2. With the field value's address (if addressable)
  3. With the dereferenced value (if the field is a pointer and not nil)

This allows the validation function to work with values, pointers, and interface implementations.

Parameters:

  • validateFunc: Function that validates a value and returns an error if invalid
  • st: The struct to validate (can be a struct, pointer to struct, or reflect.Value)
  • namePrefix: A prefix to add to all field names in errors
  • nameTag: The struct tag key to use for field names (e.g., "json"). If empty, uses Go field name
  • namesToValidate: Optional list of specific field names to validate. If empty, validates all fields

Behavior:

  • Anonymous embedded structs are flattened
  • Named sub-structs are validated recursively
  • Array and slice elements are validated individually
  • Returns a slice of FieldError for all fields that failed validation

Example:

func validateNotEmpty(val any) error {
    if s, ok := val.(string); ok && strings.TrimSpace(s) == "" {
        return errors.New("cannot be empty")
    }
    return nil
}

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

user := User{Name: "", Email: "[email protected]"}
errors := reflection.ValidateStructFields(validateNotEmpty, user, "", "json")
// errors: [FieldError{FieldName: "name", FieldError: errors.New("cannot be empty")}]

func (FieldError) Error

func (f FieldError) Error() string

Error implements the error interface, formatting the error as "FieldName: error message".

type NamedStructField

type NamedStructField struct {
	Field reflect.StructField // Type information about the field
	Name  string              // Custom name from struct tag or field name
}

NamedStructField combines field type information with a custom name. Unlike StructFieldValueName, this doesn't include the runtime value, making it suitable for type-level operations.

Example:

type Config struct {
    Host string `json:"host"`
    Port int    `json:"port"`
}
fields := reflection.FlatExportedNamedStructFields(reflect.TypeOf(Config{}), "json")
for _, field := range fields {
    fmt.Printf("%s: %s\n", field.Name, field.Field.Type)
}
// Output:
// host: string
// port: int

func FlatExportedNamedStructFields

func FlatExportedNamedStructFields(t reflect.Type, nameTag string) []NamedStructField

FlatExportedNamedStructFields returns a slice of NamedStructField of flattened struct fields, meaning that the fields of anonoymous embedded fields are flattened to the top level of the struct. The argument t can be a struct, a pointer to a struct, or a reflect.Value.

type StructFieldValue

type StructFieldValue struct {
	Field reflect.StructField // Type information about the field
	Value reflect.Value       // Runtime value of the field
}

StructFieldValue combines a struct field's type information with its runtime value. This is useful for operations that need both the field metadata and its actual value.

Example:

type User struct {
    Name string
    Age  int
}
user := User{Name: "Alice", Age: 30}
fields := reflection.FlatExportedStructFields(user)
for _, field := range fields {
    fmt.Printf("%s (%s) = %v\n", field.Field.Name, field.Field.Type, field.Value.Interface())
}
// Output:
// Name (string) = Alice
// Age (int) = 30

func FlatExportedStructFields

func FlatExportedStructFields(val any) []StructFieldValue

FlatExportedStructFields returns a slice of StructFieldValue of flattened struct fields, meaning that the fields of anonoymous embedded fields are flattened to the top level of the struct. The argument val can be a struct, a pointer to a struct, or a reflect.Value.

type StructFieldValueName

type StructFieldValueName struct {
	Field reflect.StructField // Type information about the field
	Value reflect.Value       // Runtime value of the field
	Name  string              // Custom name from struct tag or field name
}

StructFieldValueName combines field type information, runtime value, and a custom name. The Name is typically derived from a struct tag (e.g., json, db, xml tags).

Example:

type Product struct {
    ID    int    `db:"product_id"`
    Name  string `db:"product_name"`
    Price float64 `db:"price"`
}
product := Product{ID: 1, Name: "Widget", Price: 9.99}
fields := reflection.FlatExportedStructFieldValueNames(product, "db")
for _, field := range fields {
    fmt.Printf("%s = %v\n", field.Name, field.Value.Interface())
}
// Output:
// product_id = 1
// product_name = Widget
// price = 9.99

func FlatExportedStructFieldValueNames

func FlatExportedStructFieldValueNames(val any, nameTag string) []StructFieldValueName

FlatExportedStructFieldValueNames returns a slice of StructFieldValueName of flattened struct fields, meaning that the fields of anonoymous embedded fields are flattened to the top level of the struct. The argument val can be a struct, a pointer to a struct, or a reflect.Value.

Jump to

Keyboard shortcuts

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