DEV Community

Cover image for 🏷️ Sintaxe Alternativa em Go: Uma Leve Introdução às Struct Field Tags
João Oliveira
João Oliveira

Posted on

🏷️ Sintaxe Alternativa em Go: Uma Leve Introdução às Struct Field Tags

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"`
}
Enter fullscreen mode Exit fullscreen mode

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"`
}
Enter fullscreen mode Exit fullscreen mode

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"`
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

output

map[birth_year:2004 name:John] {John 0 2004}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

output

{john 21 2004}
{JOHN 21 2004}
Enter fullscreen mode Exit fullscreen mode

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.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Eliminate Context Switching and Maximize Productivity

Pieces.app

Pieces Copilot is your personalized workflow assistant, working alongside your favorite apps. Ask questions about entire repositories, generate contextualized code, save and reuse useful snippets, and streamline your development process.

Learn more

👋 Kindness is contagious

If you found this article helpful, please give a ❤️ or share a friendly comment!

Got it