Tags de struct
Nesta aula, você aprenderá sobre tags de struct em Go: como defini-las, lê-las via reflexão e aplicá-las em casos reais como serialização JSON e mapeamento de banco de dados, com exemplos práticos e exercícios.
Tags de struct são metadados anexados aos campos de uma struct em Go. Elas permitem que bibliotecas e ferramentas externas interpretem informações adicionais sobre cada campo, como nomes alternativos, regras de validação ou instruções de serialização. Embora sejam opcionais, são amplamente utilizadas em aplicações reais, especialmente com JSON, bancos de dados e validação.
Nesta aula, vamos explorar a sintaxe das tags, como lê-las usando o pacote reflect e como aplicá-las em cenários comuns, com exemplos práticos que você pode adaptar aos seus projetos.
Sintaxe
Uma tag de struct é uma string literal que aparece após o tipo de um campo, entre crases (backticks). A tag é composta por uma ou mais diretivas, cada uma com um nome e um valor opcional, separadas por espaços. A forma geral é:
type Exemplo struct {
Campo Tipo `nome:"valor" outra:"valor2"`
}O valor da tag é uma string que pode conter qualquer caractere, mas é comum usar o formato chave:"valor". As chaves são identificadores, e os valores são strings entre aspas duplas. Se houver mais de uma diretiva, elas são separadas por espaços. É importante que a tag seja escrita exatamente com crases, pois aspas duplas não funcionam para tags (a menos que sejam escapadas, o que é raro).
Exemplo prático:
type Pessoa struct {
Nome string `json:"nome"`
Idade int `json:"idade"`
Email string `json:"email,omitempty"`
}Neste exemplo, usamos a tag json para indicar como o campo deve ser serializado/desserializado pelo pacote encoding/json. A opção omitempty omite o campo se for zero.
Lendo tags via reflexão
Para acessar as tags de uma struct em tempo de execução, usamos o pacote reflect. A função reflect.TypeOf retorna o tipo da struct, e o método Field(i) retorna informações sobre o campo na posição i. A tag é obtida com o método Tag.Get(key) ou Tag.Lookup(key).
Exemplo básico:
package main
import (
"fmt"
"reflect"
)
type Pessoa struct {
Nome string `json:"nome" db:"nome"`
Idade int `json:"idade" db:"idade"`
}
func main() {
t := reflect.TypeOf(Pessoa{})
for i := 0; i < t.NumField(); i++ {
campo := t.Field(i)
fmt.Printf("Campo: %s\n", campo.Name)
fmt.Printf("Tag json: %s\n", campo.Tag.Get("json"))
fmt.Printf("Tag db: %s\n", campo.Tag.Get("db"))
fmt.Println("---")
}
}Ao executar, veremos os nomes dos campos e as tags associadas. O método Get retorna uma string vazia se a chave não existir. Já Lookup retorna dois valores: a tag e um booleano indicando se a chave foi encontrada.
Exemplo com Lookup:
tag, ok := campo.Tag.Lookup("json")
if ok {
fmt.Println("Tag json:", tag)
} else {
fmt.Println("Tag json não encontrada")
}Usos comuns (json, db)
O uso mais comum de tags é para serialização JSON, como vimos. O pacote encoding/json usa as tags json para controlar o nome do campo no JSON, opções como omitempty e string, e até mesmo ignorar o campo com json:"-".
Exemplo:
type Produto struct {
ID int `json:"id"`
Nome string `json:"nome"`
Preco float64 `json:"preco,omitempty"`
Desconto float64 `json:"-"`
Categoria string `json:"categoria,omitempty"`
}Outro uso comum é em bancos de dados, especialmente com ORMs como GORM. As tags db ou gorm indicam o nome da coluna, restrições, índices etc. Por exemplo:
type Usuario struct {
ID uint `gorm:"primaryKey"`
Nome string `gorm:"size:100;not null"`
Email string `gorm:"uniqueIndex;not null"`
CreatedAt time.Time
}Aqui, a tag gorm define a chave primária, o tamanho do campo, a restrição NOT NULL e um índice único. O GORM lê essas tags para criar a tabela e as operações de banco.
Exemplos
Vamos consolidar com exemplos completos. Primeiro, um exemplo com JSON:
package main
import (
"encoding/json"
"fmt"
)
type Pessoa struct {
Nome string `json:"nome"`
Idade int `json:"idade"`
Email string `json:"email,omitempty"`
Senha string `json:"-"`
}
func main() {
p := Pessoa{
Nome: "Maria",
Idade: 30,
Email: "maria@example.com",
Senha: "secreta",
}
dados, _ := json.Marshal(p)
fmt.Println(string(dados))
}
Saída: {"nome":"Maria","idade":30,"email":"maria@example.com"} (a senha é ignorada).
Segundo, um exemplo de leitura de tags via reflexão para criar um mapeamento genérico:
package main
import (
"fmt"
"reflect"
)
type Config struct {
Host string `env:"HOST" def:"localhost"`
Port int `env:"PORT" def:"8080"`
}
func main() {
t := reflect.TypeOf(Config{})
v := reflect.ValueOf(Config{})
for i := 0; i < t.NumField(); i++ {
campo := t.Field(i)
valor := v.Field(i)
env := campo.Tag.Get("env")
def := campo.Tag.Get("def")
fmt.Printf("%s: env=%s def=%s valor=%v\n", campo.Name, env, def, valor.Interface())
}
}Isso pode ser usado para preencher campos a partir de variáveis de ambiente, com valores padrão.
Boas práticas
Ao usar tags, siga estas recomendações:
- Use tags consistentes e documentadas.
- Prefira chaves padronizadas (ex.:
json,db,validate) para evitar conflitos. - Evite tags desnecessárias; mantenha as structs limpas.
- Para bibliotecas próprias, defina nomes de tags com prefixo (ex.:
mylib:"..."). - Teste a leitura de tags com reflexão quando criar ferramentas genéricas.
Referências
- Go Specification - Struct types
- reflect.StructTag - Go Documentation
- encoding/json - Go Documentation
- GORM Models - Tags
- JSON and Go - Go Blog
- go-playground/validator - Tags de validação
Exercícios
Defina uma struct
Livrocom camposTitulo,Autor,AnoeISBN. Use tags JSON para que os campos sejam serializados comotitulo,autor,anoeisbn, e faça com queISBNseja omitido se vazio.✓ Resposta:type Livro struct { Titulo string `json:"titulo"` Autor string `json:"autor"` Ano int `json:"ano"` ISBN string `json:"isbn,omitempty"` }Escreva uma função que recebe uma struct qualquer e imprime todas as tags
jsonde seus campos, no formatoNomeCampo: tag.✓ Resposta:func imprimirTagsJSON(s interface{}) { t := reflect.TypeOf(s) if t.Kind() != reflect.Struct { fmt.Println("Não é uma struct") return } for i := 0; i < t.NumField(); i++ { campo := t.Field(i) tag := campo.Tag.Get("json") fmt.Printf("%s: %s\n", campo.Name, tag) } }Usando a struct do exercício 1, serialize uma instância com JSON e mostre a saída. O que acontece se
ISBNestiver vazio?✓ Resposta:livro := Livro{Titulo: "O Senhor dos Anéis", Autor: "J.R.R. Tolkien", Ano: 1954} dados, _ := json.Marshal(livro) fmt.Println(string(dados)) // Saída: {"titulo":"O Senhor dos Anéis","autor":"J.R.R. Tolkien","ano":1954} // O campo isbn é omitido porque está vazio.Crie uma struct
Configcom tagsenvedefault. Escreva um código que lê as tags e imprime o nome da variável de ambiente e o valor padrão para cada campo.✓ Resposta:type Config struct { Host string `env:"HOST" default:"localhost"` Port int `env:"PORT" default:"8080"` } func main() { t := reflect.TypeOf(Config{}) for i := 0; i < t.NumField(); i++ { campo := t.Field(i) env := campo.Tag.Get("env") def := campo.Tag.Get("default") fmt.Printf("Campo %s: env=%s default=%s\n", campo.Name, env, def) } }Explique a diferença entre os métodos
GeteLookupdereflect.StructTage dê um exemplo de uso deLookup.✓ Resposta:Getretorna apenas a string (vazia se não existir).Lookupretorna a string e um booleano indicando se a chave existe. Exemplo:tag, ok := campo.Tag.Lookup("json") if ok { fmt.Println("Tag encontrada:", tag) } else { fmt.Println("Tag não encontrada") }