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
- Use Standard Tags: Prefer json, db, xml, yaml
- Be Consistent: Use same naming across tags
- Document Tags: Explain custom tag meanings
- Validate Tags: Check tag syntax
- Cache Reflection: Don’t parse tags repeatedly
- Use Omitempty: For optional fields
- Handle Errors: Validate tag values
- 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