Skip to main content
โšก Calmops

Struct Tags and Metadata

Struct Tags and Metadata

Struct tags provide a way to attach metadata to struct fields. They’re essential for serialization, validation, and database mapping.

Struct Tag Basics

Tags are string literals attached to struct fields.

Good: Using Struct Tags

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Email string `json:"email,omitempty"`
}

func main() {
	p := Person{Name: "Alice", Age: 30, Email: "[email protected]"}
	
	// Marshal to JSON
	data, _ := json.Marshal(p)
	fmt.Println(string(data))
	
	// Unmarshal from JSON
	var p2 Person
	json.Unmarshal([]byte(`{"name":"Bob","age":25}`), &p2)
	fmt.Printf("%+v\n", p2)
}

Common Tag Types

JSON Tags

package main

import (
	"encoding/json"
	"fmt"
)

type Product struct {
	ID       int    `json:"id"`
	Name     string `json:"name"`
	Price    float64 `json:"price"`
	Hidden   string `json:"-"` // Ignored
	Optional string `json:"optional,omitempty"` // Omitted if empty
}

func main() {
	p := Product{
		ID:     1,
		Name:   "Widget",
		Price:  9.99,
		Hidden: "secret",
	}
	
	data, _ := json.MarshalIndent(p, "", "  ")
	fmt.Println(string(data))
}

Database Tags

package main

import (
	"fmt"
)

type User struct {
	ID    int    `db:"id" json:"id"`
	Name  string `db:"name" json:"name"`
	Email string `db:"email" json:"email"`
	Age   int    `db:"age" json:"age"`
}

func main() {
	u := User{ID: 1, Name: "Alice", Email: "[email protected]", Age: 30}
	fmt.Printf("%+v\n", u)
}

Validation Tags

package main

import (
	"fmt"
)

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

func main() {
	a := Account{
		Username: "alice",
		Email:    "[email protected]",
		Age:      30,
	}
	fmt.Printf("%+v\n", a)
}

Parsing Tags

Extract and use tag information.

Good: Tag Parsing

package main

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

type Person struct {
	Name string `json:"name" validate:"required"`
	Age  int    `json:"age" validate:"required,min=0,max=150"`
	Email string `json:"email" validate:"email"`
}

func parseTags(v interface{}) {
	t := reflect.TypeOf(v)
	
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		
		jsonTag := field.Tag.Get("json")
		validateTag := field.Tag.Get("validate")
		
		fmt.Printf("Field: %s\n", field.Name)
		fmt.Printf("  JSON: %s\n", jsonTag)
		fmt.Printf("  Validate: %s\n", validateTag)
		
		// Parse validate tag
		if validateTag != "" {
			rules := strings.Split(validateTag, ",")
			fmt.Printf("  Rules: %v\n", rules)
		}
	}
}

func main() {
	p := Person{}
	parseTags(p)
}

Custom Tag Handling

Create and use custom tags.

Good: Custom Tags

package main

import (
	"fmt"
	"reflect"
)

type Config struct {
	Host     string `config:"host" default:"localhost"`
	Port     int    `config:"port" default:"8080"`
	Debug    bool   `config:"debug" default:"false"`
	Timeout  int    `config:"timeout" default:"30"`
}

func loadConfig(v interface{}) map[string]interface{} {
	result := make(map[string]interface{})
	t := reflect.TypeOf(v)
	
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		
		configName := field.Tag.Get("config")
		defaultVal := field.Tag.Get("default")
		
		if configName != "" {
			result[configName] = defaultVal
		}
	}
	
	return result
}

func main() {
	cfg := Config{}
	config := loadConfig(cfg)
	
	for key, val := range config {
		fmt.Printf("%s: %v\n", key, val)
	}
}

Tag Validation

Validate struct fields based on tags.

Good: Tag-Based Validation

package main

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

type User struct {
	Name string `validate:"required,min=3,max=50"`
	Age  int    `validate:"required,min=0,max=150"`
	Email string `validate:"required"`
}

func validate(v interface{}) []string {
	var errors []string
	t := reflect.TypeOf(v)
	val := reflect.ValueOf(v)
	
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fieldVal := val.Field(i)
		
		validateTag := field.Tag.Get("validate")
		if validateTag == "" {
			continue
		}
		
		rules := strings.Split(validateTag, ",")
		
		for _, rule := range rules {
			parts := strings.Split(rule, "=")
			ruleName := parts[0]
			
			switch ruleName {
			case "required":
				if fieldVal.IsZero() {
					errors = append(errors, fmt.Sprintf("%s is required", field.Name))
				}
			case "min":
				if len(parts) > 1 {
					minVal, _ := strconv.Atoi(parts[1])
					if fieldVal.Kind() == reflect.Int && fieldVal.Int() < int64(minVal) {
						errors = append(errors, fmt.Sprintf("%s must be >= %d", field.Name, minVal))
					}
				}
			case "max":
				if len(parts) > 1 {
					maxVal, _ := strconv.Atoi(parts[1])
					if fieldVal.Kind() == reflect.Int && fieldVal.Int() > int64(maxVal) {
						errors = append(errors, fmt.Sprintf("%s must be <= %d", field.Name, maxVal))
					}
				}
			}
		}
	}
	
	return errors
}

func main() {
	u := User{Name: "Al", Age: 200}
	
	errors := validate(u)
	for _, err := range errors {
		fmt.Println("Error:", err)
	}
}

Multiple Tags

Use multiple tags on a single field.

Good: Multiple Tags

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)

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:"product_price" csv:"price"`
}

func extractTags(v interface{}) {
	t := reflect.TypeOf(v)
	
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		
		fmt.Printf("Field: %s\n", field.Name)
		fmt.Printf("  JSON: %s\n", field.Tag.Get("json"))
		fmt.Printf("  DB: %s\n", field.Tag.Get("db"))
		fmt.Printf("  CSV: %s\n", field.Tag.Get("csv"))
	}
}

func main() {
	p := Product{}
	extractTags(p)
	
	// JSON marshaling
	p = Product{ID: 1, Name: "Widget", Price: 9.99}
	data, _ := json.Marshal(p)
	fmt.Println("JSON:", string(data))
}

Tag Best Practices

Good: Well-Structured Tags

package main

import (
	"encoding/json"
	"fmt"
)

type Article struct {
	ID        int    `json:"id" db:"id" csv:"id"`
	Title     string `json:"title" db:"title" csv:"title" validate:"required,min=5"`
	Content   string `json:"content" db:"content" csv:"content" validate:"required"`
	Author    string `json:"author" db:"author" csv:"author"`
	Published bool   `json:"published" db:"published" csv:"published"`
}

func main() {
	a := Article{
		ID:        1,
		Title:     "Go Struct Tags",
		Content:   "Learn about struct tags...",
		Author:    "Alice",
		Published: true,
	}
	
	data, _ := json.MarshalIndent(a, "", "  ")
	fmt.Println(string(data))
}

Bad: Inconsistent Tags

// โŒ AVOID: Inconsistent tag naming
package main

type User struct {
	ID    int    `json:"id" db:"user_id" csv:"ID"`
	Name  string `json:"name" db:"user_name" csv:"NAME"`
	Email string `json:"email" db:"user_email" csv:"EMAIL"`
}

Common Tag Conventions

package main

type Example struct {
	// JSON serialization
	Field1 string `json:"field1"`
	
	// Database mapping
	Field2 string `db:"field_2"`
	
	// CSV export
	Field3 string `csv:"field3"`
	
	// Validation rules
	Field4 string `validate:"required,email"`
	
	// XML serialization
	Field5 string `xml:"field5"`
	
	// YAML serialization
	Field6 string `yaml:"field6"`
	
	// Ignored fields
	Field7 string `json:"-" db:"-"`
}

Best Practices

  1. Use Standard Tags: Prefer json, db, xml, yaml
  2. Be Consistent: Use same naming across tags
  3. Document Tags: Explain custom tag meanings
  4. Validate Tags: Check tag syntax
  5. Cache Reflection: Don’t parse tags repeatedly
  6. Use Omitempty: For optional fields
  7. Handle Errors: Validate tag values
  8. Test Tags: Ensure tags work as expected

Common Pitfalls

  • Typos in Tags: Easy to misspell tag names
  • Inconsistent Naming: Different conventions per tag
  • Ignoring Errors: Not validating tag values
  • Performance: Parsing tags repeatedly
  • Complexity: Too many custom tags

Resources

Summary

Struct tags provide metadata for serialization, validation, and database mapping. Use standard tags like json, db, and xml. Parse tags using reflection when needed. Keep tags consistent and well-documented. Cache tag parsing results for performance.

Comments