BudiBadu Logo

Samplebadu

Code with Example
BudiBadu Logo
Samplebadu

Go by Example: Tagged Struct Fields

Go 1.23

Struct tags are metadata attached to fields, used heavily by libraries like `encoding/json` and `validator`. This example explores how to define custom tags and parse them using reflection.

Code

package main

import (
    "fmt"
    "reflect"
    "strings"
)

// User struct with custom validation tags
type User struct {
    Username string `validate:"required,min=3"`
    Email    string `validate:"required,email"`
    Age      int    `validate:"min=18"`
}

// Simple validator function that parses our custom tags
func validate(u interface{}) []string {
    var errors []string
    v := reflect.ValueOf(u)
    t := reflect.TypeOf(u)

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        tag := field.Tag.Get("validate")

        if tag == "" {
            continue
        }

        rules := strings.Split(tag, ",")
        for _, rule := range rules {
            if rule == "required" {
                if value.IsZero() {
                    errors = append(errors, fmt.Sprintf("%s is required", field.Name))
                }
            }
            // (Simplified: real validation logic would go here for min, email, etc.)
        }
    }
    return errors
}

func main() {
    // 1. Valid User
    u1 := User{Username: "gopher", Email: "[email protected]", Age: 25}
    fmt.Println("u1 errors:", validate(u1))

    // 2. Invalid User (Empty fields)
    u2 := User{Age: 10}
    fmt.Println("u2 errors:", validate(u2))
    
    // 3. Inspecting Tags
    field, _ := reflect.TypeOf(User{}).FieldByName("Email")
    fmt.Println("Email tag:", field.Tag)
}

Explanation

Tagged struct fields in Go represent a sophisticated metaprogramming feature that allows developers to attach arbitrary string metadata to struct fields, which can then be introspected at runtime using the reflect package to alter program behavior dynamically. While the Go compiler itself ignores the content of these tags, they form the backbone of the language's ecosystem for serialization (like JSON or XML), database object mapping (ORM), and input validation, effectively enabling declarative programming patterns within a statically typed language. By defining tags as key-value pairs enclosed in backticks, developers can specify constraints, formatting rules, or mapping directives that are decoupled from the business logic but tightly integrated with the data definition.

The mechanism for accessing these tags involves utilizing Go's reflection API, specifically reflect.Type, to iterate over the fields of a struct and retrieve the tag string associated with a specific key using the Get or Lookup methods. This capability allows library authors to build powerful, generic tools that can operate on any user-defined struct without knowing its layout at compile time, such as a validator that automatically checks if a field meets "required" or "max length" criteria based solely on its tag. This pattern significantly reduces boilerplate code and enhances maintainability by centralizing configuration directly on the data model.

  • Metadata: Key-value pairs in backticks (e.g., key:"value").
  • Reflection: Use reflect.TypeOf() to access tags.
  • Ecosystem: Used by JSON, XML, ORMs, and validators.
  • Decoupling: Separates configuration from logic.

Code Breakdown

11-13
Defining custom tags. We attach a 'validate' tag to each field. The value is a comma-separated string of rules (e.g., "required,min=3"). This string has no inherent meaning to Go; our code assigns meaning to it.
19-20
Reflection setup. 'reflect.ValueOf' gives us the data (values), and 'reflect.TypeOf' gives us the schema (types, field names, and tags). We need both to validate the struct.
25
Reading the tag. 'field.Tag.Get("validate")' extracts the value associated with the "validate" key. If the tag is missing, it returns an empty string.
34
Checking the rule. We use 'value.IsZero()' to check if the field has its zero value (e.g., "" for string, 0 for int). This is a generic way to check for "empty" fields across different types.