
How to Use Struct Tags in Go – Metadata for Struct Fields
Struct tags in Go are a powerful metadata system that lets you annotate struct fields with additional information used by packages for serialization, validation, database mapping, and more. They’re essential for building robust applications that interact with JSON APIs, databases, and configuration systems. This post will show you how to effectively use struct tags, cover common patterns you’ll encounter in real-world Go development, and help you avoid the typical mistakes that can cause headaches down the road.
How Struct Tags Work
Struct tags are string literals that follow struct field declarations. Go’s reflection package can read these tags at runtime, allowing libraries to modify their behavior based on the metadata you provide.
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"`
Password string `json:"-" db:"password_hash"`
}
The syntax follows a specific pattern: `key:"value" key2:"value2"`
. Each key-value pair is separated by spaces, and the reflection package parses these during runtime. The official Go documentation provides detailed information about the StructTag type and its methods.
Here’s how you’d access struct tags programmatically:
package main
import (
"fmt"
"reflect"
)
type Product struct {
Name string `json:"name" validate:"required"`
Price int `json:"price" validate:"min=0"`
}
func main() {
p := Product{}
t := reflect.TypeOf(p)
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, JSON: %s, Validate: %s\n",
field.Name, jsonTag, validateTag)
}
}
Common Struct Tag Use Cases
Let's dive into the most frequent applications you'll encounter when building Go applications.
JSON Serialization
The json
tag is probably the most widely used struct tag. It controls how the encoding/json
package handles marshaling and unmarshaling:
type APIResponse struct {
UserID int `json:"user_id"`
Username string `json:"username"`
Email string `json:"email"`
CreatedAt string `json:"created_at"`
Password string `json:"-"` // Never serialize
TempData string `json:"temp,omitempty"` // Omit if empty
Internal string `json:",omitempty"` // Keep field name, omit if empty
}
Key JSON tag options:
json:"field_name"
- Custom field name in JSONjson:"-"
- Exclude field from JSON entirelyjson:",omitempty"
- Omit field if it has zero valuejson:",string"
- Convert numeric/boolean values to strings
Database Mapping
Popular ORMs like GORM use struct tags to map Go structs to database tables:
type Order struct {
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
CustomerID uint `gorm:"not null;index" json:"customer_id"`
Total float64 `gorm:"type:decimal(10,2)" json:"total"`
Status string `gorm:"size:50;default:pending" json:"status"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
Validation
Libraries like go-playground/validator
use struct tags to define validation rules:
type SignupRequest struct {
Username string `json:"username" validate:"required,min=3,max=20,alphanum"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=18,lte=100"`
Website string `json:"website" validate:"omitempty,url"`
}
Advanced Struct Tag Patterns
Custom Tag Processing
You can create your own tag processing logic for specialized use cases:
package main
import (
"fmt"
"reflect"
"strings"
)
type Config struct {
DatabaseURL string `env:"DATABASE_URL" default:"localhost:5432"`
Port int `env:"PORT" default:"8080"`
Debug bool `env:"DEBUG" default:"false"`
}
func processEnvTags(v interface{}) {
val := reflect.ValueOf(v).Elem()
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
structField := typ.Field(i)
envTag := structField.Tag.Get("env")
defaultTag := structField.Tag.Get("default")
if envTag != "" {
fmt.Printf("Would read %s from environment, default: %s\n",
envTag, defaultTag)
}
}
}
func main() {
config := &Config{}
processEnvTags(config)
}
Multi-Tag Coordination
Real applications often combine multiple tag systems. Here's a comprehensive example:
type Employee struct {
ID int `json:"id" gorm:"primaryKey" validate:"-"`
FirstName string `json:"first_name" gorm:"size:100;not null" validate:"required,min=2"`
LastName string `json:"last_name" gorm:"size:100;not null" validate:"required,min=2"`
Email string `json:"email" gorm:"uniqueIndex;size:255" validate:"required,email"`
PhoneNumber string `json:"phone" gorm:"size:20" validate:"omitempty,e164"`
Salary int `json:"-" gorm:"type:int" validate:"gte=0"`
Department string `json:"department" gorm:"size:50" validate:"required,oneof=engineering marketing sales hr"`
StartDate time.Time `json:"start_date" gorm:"not null" validate:"required"`
IsActive bool `json:"is_active" gorm:"default:true" validate:"-"`
}
Performance Considerations and Benchmarks
Struct tag processing involves reflection, which has performance implications. Here's a comparison of different approaches:
Method | Operations/sec | Memory/op | Allocations/op |
---|---|---|---|
Direct field access | 1,000,000,000 | 0 B | 0 |
Reflection with caching | 50,000,000 | 24 B | 1 |
Reflection without caching | 5,000,000 | 256 B | 8 |
To minimize performance impact:
// Cache reflection results
var structCache = make(map[reflect.Type][]reflect.StructField)
func getFieldsWithTag(t reflect.Type, tagName string) []reflect.StructField {
if fields, exists := structCache[t]; exists {
return fields
}
var fields []reflect.StructField
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Tag.Get(tagName) != "" {
fields = append(fields, field)
}
}
structCache[t] = fields
return fields
}
Common Pitfalls and Troubleshooting
Tag Syntax Errors
Malformed tags are silently ignored, making debugging difficult:
type BadExample struct {
// Wrong: missing quotes around key
Field1 string `json:name`
// Wrong: using single quotes
Field2 string `json:'email'`
// Wrong: no space between tags
Field3 string `json:"id"validate:"required"`
// Correct
Field4 string `json:"name" validate:"required"`
}
Use go vet
to catch some tag issues:
go vet ./...
Tag Inheritance and Embedding
Embedded struct tags behave in specific ways:
type BaseModel struct {
ID int `json:"id" gorm:"primaryKey"`
CreatedAt time.Time `json:"created_at"`
}
type User struct {
BaseModel // Tags are inherited
Name string `json:"name"`
Email string `json:"email"`
}
// Anonymous field tags are promoted
type Product struct {
BaseModel `json:",inline"` // Flatten in JSON
Title string `json:"title"`
Price int `json:"price"`
}
Pointer vs Value Receivers
Tag processing behavior differs with pointers:
func processStruct(v interface{}) {
val := reflect.ValueOf(v)
// Handle pointer to struct
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
fmt.Println("Not a struct")
return
}
// Process fields...
}
Real-World Integration Examples
REST API with Validation
Here's a practical example combining JSON serialization with validation:
package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/go-playground/validator/v10"
)
type CreateUserRequest struct {
Username string `json:"username" validate:"required,min=3,max=20"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=18,lte=120"`
}
var validate = validator.New()
func createUserHandler(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
if err := validate.Struct(req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Process valid request...
fmt.Fprintf(w, "User %s created successfully", req.Username)
}
Configuration Management
Struct tags excel at configuration parsing:
type ServerConfig struct {
Host string `env:"HOST" default:"localhost" validate:"required"`
Port int `env:"PORT" default:"8080" validate:"min=1,max=65535"`
DatabaseURL string `env:"DATABASE_URL" validate:"required,url"`
RedisURL string `env:"REDIS_URL" validate:"omitempty,url"`
LogLevel string `env:"LOG_LEVEL" default:"info" validate:"oneof=debug info warn error"`
EnableCORS bool `env:"ENABLE_CORS" default:"true"`
}
Best Practices
- Keep tag values consistent across your codebase - establish naming conventions early
- Use meaningful tag names that clearly indicate their purpose
- Document custom tags in your team's coding standards
- Cache reflection results in performance-critical applications
- Validate tag syntax with tools like
go vet
- Consider using code generation for complex tag processing to avoid runtime reflection overhead
- Test tag behavior thoroughly, especially when combining multiple tag systems
Struct tags are indispensable for modern Go development, enabling clean separation between your domain models and external representations. Whether you're building REST APIs, working with databases, or processing configuration files, mastering struct tags will make your code more maintainable and robust. The key is understanding how different libraries interpret tags and establishing consistent patterns across your applications.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.