Certamente, se você já escreveu ou apenas parou para ler algum código em Go, com certeza já deve ter encontrado algo escrito ao lado do nome de um campo em uma struct, mas afinal, o que é aquilo??? type People struct { ID uuid.UUID `json:"id,omitempty"` Name string `json:"name,omitempty"` Age uint8 `json:"age,omitempty"` BirthYear uint16 `json:"birth_year,omitempty"` } Isso são anotações de campos de uma estrutura, conhecidas como struct field tags, as quais podem adicionar ou não comportamentos extras aos campos. Estrutura de uma tag Toda tag é composta por: ` ` - multiline string. chave:valor - semelhante a um map, o valor geralmente é uma string. no final ficamos com: `chave:"valor"` Cada pacote implementa a leitura dos valores de forma diferente. O pacote encoding define como separação de valores ,, enquanto o GORM define como separador o ;. Exemplo: type Author struct { ID uuid.UUID `json:"id,omitempty"` Name string `json:"name,omitempty"` } assim como as tags podem ter diversos valores, cada campo pode ter diversas tags também. Exemplo: type Author struct { ID uuid.UUID `json:"id,omitempty" gorm:"primaryKey;type:uuid;default:gen_random_uuid()"` Name string `json:"name,omitempty" binding:"required,min=2" gorm:"type:varchar(255);column:name;unique;not null"` } No exemplo acima, ID tem as tags json e gorm, enquanto Name tem as tags json, gorm e binding(faz parte do pacote validate). Usando o pacote encoding/json para entender mais sobre as tags Imagine uma requisição a uma API onde você tem que passar um JSON no corpo da requisição. Esse JSON será processado pelo seu código, mas o padrão de nomenclatura dos campos do JSON pode ser diferente do definido em seu código. Você pode ver comumente pela web o uso de camelCase, mas também outros usando snake_case. Portanto, é necessário dizer ao seu código como lidar com esse tipo de problema. Exemplo package main import ( "encoding/json" "fmt" "log" ) type People struct { Name string `json:"name,omitempty"` Age uint8 `json:"age,omitempty"` BirthYear uint16 `json:"birth_year,omitempty"` } func main() { // Simula entrada de um JSON binário (serialização). p1 := People{ Name: "John", BirthYear: 2004, } bJson, err := json.Marshal(p1) if err != nil { log.Fatal(err) } var jsonMap map[string]any // Desserializa o JSON binário dentro de um map. if err := json.Unmarshal(bJson, &jsonMap); err != nil { log.Fatal(err) } var jsonStruct People // Desserializa o JSON binário dentro de uma struct. if err := json.Unmarshal(bJson, &jsonStruct); err != nil { log.Fatal(err) } fmt.Println(jsonMap, jsonStruct) } output map[birth_year:2004 name:John] {John 0 2004} Quando o pacote json fez a desserialização no map temos um map com as chaves birth_year e name, mas sem a presença do campo age devido a anotação json:"omitempty", agora a desserialização na struct o pacote json já sabia qual campo atribuir a birthYear, pois BirthYear contém a anotação json:"birth_year" Criando uma tag personalizada Para criar uma custom tag, necessitamos usar o pacote reflect para ter acesso não só as tags, mas também aos campos. Exemplo package main import ( "fmt" "reflect" "strings" ) type People struct { Name string `json:"name,omitempty" myTag:"upper"` Age uint8 `json:"age,omitempty"` BirthYear uint16 `json:"birth_year,omitempty"` } func (p *People) Validate() { t := reflect.TypeOf(*p) // Itéra sobre o número de campos na struct for i := range t.NumField() { // Verifica se o campo contém a tag myTag if _, ok := t.Field(i).Tag.Lookup("myTag"); ok == true { fieldName := t.Field(i).Name // pega o valor e o define com comportamento upper addr := reflect.ValueOf(p).Elem().FieldByName(fieldName) addr.SetString(strings.ToUpper(addr.String())) } } } func main() { p1 := People{ Name: "john", Age: 21, BirthYear: 2004, } fmt.Println(p1) p1.Validate() fmt.Println(p1) } output {john 21 2004} {JOHN 21 2004} Para esse exemplo, usei myTag, que tem o valor upper que transforma o campo Name para maiúsculo. Como o intuito do exemplo é apenas mostrar como usar uma custom tag, fiz o código mais simples possível, portanto algumas partes necessárias de verificação no código não foram implementadas. Observação O pacote reflect deve ser usado com cautela, pois pode afetar a performance da aplicação.

Mar 24, 2025 - 22:35
 0

Certamente, se você já escreveu ou apenas parou para ler algum código em Go, com certeza já deve ter encontrado algo escrito ao lado do nome de um campo em uma struct, mas afinal, o que é aquilo???

type People struct {
    ID uuid.UUID `json:"id,omitempty"`
    Name string `json:"name,omitempty"`
    Age uint8 `json:"age,omitempty"`
    BirthYear uint16 `json:"birth_year,omitempty"`
}

Isso são anotações de campos de uma estrutura, conhecidas como struct field tags, as quais podem adicionar ou não comportamentos extras aos campos.

Estrutura de uma tag

  1. Toda tag é composta por:
  • ` ` - multiline string.

  • chave:valor - semelhante a um map, o valor geralmente é uma string.

  1. no final ficamos com:
  • `chave:"valor"`

Cada pacote implementa a leitura dos valores de forma diferente. O pacote encoding define como separação de valores ,, enquanto o GORM define como separador o ;.

Exemplo:

type Author struct {
    ID   uuid.UUID `json:"id,omitempty"`
    Name string    `json:"name,omitempty"`
}

assim como as tags podem ter diversos valores, cada campo pode ter diversas tags também.

Exemplo:

type Author struct {
    ID   uuid.UUID `json:"id,omitempty" gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
    Name string    `json:"name,omitempty" binding:"required,min=2" gorm:"type:varchar(255);column:name;unique;not null"`
}

No exemplo acima, ID tem as tags json e gorm, enquanto Name tem as tags json, gorm e binding(faz parte do pacote validate).

Usando o pacote encoding/json para entender mais sobre as tags

Imagine uma requisição a uma API onde você tem que passar um JSON no corpo da requisição. Esse JSON será processado pelo seu código, mas o padrão de nomenclatura dos campos do JSON pode ser diferente do definido em seu código. Você pode ver comumente pela web o uso de camelCase, mas também outros usando snake_case. Portanto, é necessário dizer ao seu código como lidar com esse tipo de problema.

Exemplo

package main

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

type People struct {
    Name      string `json:"name,omitempty"`
    Age       uint8  `json:"age,omitempty"`
    BirthYear uint16 `json:"birth_year,omitempty"`
}

func main() {
    // Simula entrada de um JSON binário (serialização).
    p1 := People{
        Name:      "John",
        BirthYear: 2004,
    }

    bJson, err := json.Marshal(p1)

    if err != nil {
        log.Fatal(err)
    }

    var jsonMap map[string]any

    // Desserializa o JSON binário dentro de um map.
    if err := json.Unmarshal(bJson, &jsonMap); err != nil {
        log.Fatal(err)
    }

    var jsonStruct People

    // Desserializa o JSON binário dentro de uma struct.
    if err := json.Unmarshal(bJson, &jsonStruct); err != nil {
        log.Fatal(err)
    }

    fmt.Println(jsonMap, jsonStruct)
}

output

map[birth_year:2004 name:John] {John 0 2004}

Quando o pacote json fez a desserialização no map temos um map com as chaves birth_year e name, mas sem a presença do campo age devido a anotação json:"omitempty", agora a desserialização na struct o pacote json já sabia qual campo atribuir a birthYear, pois BirthYear contém a anotação json:"birth_year"

Criando uma tag personalizada

Para criar uma custom tag, necessitamos usar o pacote reflect para ter acesso não só as tags, mas também aos campos.

Exemplo

package main

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

type People struct {
    Name      string `json:"name,omitempty"       myTag:"upper"`
    Age       uint8  `json:"age,omitempty"`
    BirthYear uint16 `json:"birth_year,omitempty"`
}

func (p *People) Validate() {
    t := reflect.TypeOf(*p)

    // Itéra sobre o número de campos na struct
    for i := range t.NumField() {
        // Verifica se o campo contém a tag myTag
        if _, ok := t.Field(i).Tag.Lookup("myTag"); ok == true {
            fieldName := t.Field(i).Name
            // pega o valor e o define com comportamento upper
            addr := reflect.ValueOf(p).Elem().FieldByName(fieldName)
            addr.SetString(strings.ToUpper(addr.String()))
        }
    }
}

func main() {
    p1 := People{
        Name:      "john",
        Age:       21,
        BirthYear: 2004,
    }

    fmt.Println(p1)

    p1.Validate()

    fmt.Println(p1)
}

output

{john 21 2004}
{JOHN 21 2004}

Para esse exemplo, usei myTag, que tem o valor upper que transforma o campo Name para maiúsculo. Como o intuito do exemplo é apenas mostrar como usar uma custom tag, fiz o código mais simples possível, portanto algumas partes necessárias de verificação no código não foram implementadas.

Observação

O pacote reflect deve ser usado com cautela, pois pode afetar a performance da aplicação.