As estruturas, ou structs, são usadas para coletar vários fragmentos de informações em uma unidade. Essas coletas de informações são usadas para descrever conceitos de nível superior, como um Address
composto de uma Street
, City
, State
e PostalCode
. Ao ler essas informações a partir de sistemas como bancos de dados, ou APIs, você pode usar tags struct para controlar como essa informação é atribuída aos campos de uma struct. As tags struct são pequenos fragmentos de metadados, anexados aos campos de uma struct - que fornecem instruções para outro código em Go - que funciona com a struct.
As tags struct do Go são anotações que aparecem após o tipo, em uma declaração struct em Go. Cada tag é composta por strings curtas, associadas a um valor correspondente.
Uma tag struct se parece com isso, com a tag deslocada com caracteres de crase (backtick) `````:
type User struct {
Name string `example:"name"`
}
Assim, outro código em Go consegue examinar essas structs e extrair os valores atribuídos a chaves específicas que ele solicita. As tags struct não têm efeito sobre a operação do seu código sem algum outro código que as examine.
Teste este exemplo, para ver como as tags de struct se parecem e para ver que, sem o código de outro pacote, elas não têm efeito.
package main
import "fmt"
type User struct {
Name string `example:"name"`
}
func (u *User) String() string {
return fmt.Sprintf("Hi! My name is %s", u.Name)
}
func main() {
u := &User{
Name: "Sammy",
}
fmt.Println(u)
}
Isso resultará em:
OutputHi! My name is Sammy
Esse exemplo define um tipo User
com um campo Name
. O campo Name
recebeu uma tag struct do example:"name"
. Teríamos que nos referir a essa tag específica na conversa como “exemplo de tag de struct”, pois ela usa a palavra “exemplo” como sua chave. A tag de struct example
tem o valor "name"
no campo Name
. No tipo User
, também definimos o método String()
necessário através da interface fmt.Stringer
. Isso será chamado automaticamente quando enviarmos o tipo para o fmt.Println
e nos dará a chance de produzir uma versão bem formatada da nossa struct.
Dentro do corpo de main
, criamos uma nova instância do nosso tipo User
e a enviamos para o fmt.Println
. Embora a struct tenha um identificador de struct presente, vemos que ela não tem efeito na operação deste código do Go. Ela se comportará exatamente da mesma forma se o identificador da struct não estiver presente.
Para usar os identificadores de struct para conseguir algo, será necessário que outro código do Go seja escrito para examinar as structs no tempo de execução. A biblioteca padrão tem pacotes que usam identificadores de struct como parte da sua operação. O mais popular deles é o pacote encoding/json
.
JavaScript Object Notation (JSON) é um formato textual para a codificação de coletas de dados organizados em diferentes chaves de string. É comumente usado para comunicar dados entre programas diferentes, uma vez que o formato é bastante simples, a ponto de haver bibliotecas para decodificá-lo em muitas linguagens diferentes. A seguir, temos um exemplo do JSON:
{
"language": "Go",
"mascot": "Gopher"
}
Esse objeto JSON contém duas chaves, language
e mascot
. Depois dessas chaves estão os valores associados. Aqui, a chave language
tem um valor de Go
e o valor Gopher
foi atribuído à chave mascot
.
O codificador JSON na biblioteca padrão usa os identificadores de struct como anotações que indicam ao codificador como você gostaria de nomear seus campos na saída em JSON. Esses mecanismos de codificação e decodificação do JSON podem ser encontrados no pacote encoding/json
.
Experimente este exemplo para ver como JSON está codificado sem os identificadores de struct:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string
Password string
PreferredFish []string
CreatedAt time.Time
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Isso imprimirá o seguinte resultado:
Output{
"Name": "Sammy the Shark",
"Password": "fisharegreat",
"CreatedAt": "2019-09-23T15:50:01.203059-04:00"
}
Definimos uma struct que descreve um usuário com campos, incluindo seu nome, senha e o momento em que o usuário foi criado. Dentro da função main
, criamos uma instância desse usuário, fornecendo valores para todos os campos, exceto PreferredFish
(Sammy gosta de todos os peixes). Então, enviamos a instância do User
para a função json.MarshalIndent
. Isso é usado para que possamos ver mais facilmente o resultado do JSON sem usar uma ferramenta de formatação externa. Essa chamada poderia ser substituída por json.Marshal(u)
para receber o JSON sem qualquer espaço em branco adicional. Os dois argumentos adicionais para o json.MarshalIndent
controlam o prefixo para a saída (que omitimos com a string vazia) e os caracteres que são usados para o recuo, que aqui são dois caracteres de espaço. Quaisquer erros produzido a partir do json.MarshalIndent
ficam registrados e o programa se encerra usando o os.Exit(1)
. Por fim, lançamos o []byte
retornado do json.MarshalIndent
para uma string
e entregamos a string resultante para o fmt.Println
, para impressão no terminal.
Os campos da struct aparecem exatamente como as nomeamos. Esse não é o estilo típico que talvez você espere do JSON, o qual usa o camel casing para os nomes dos campos. [Nota: na convenção Camel-Case, a primeira letra da primeira palavra fica em minúscula e a primeira letra de todas as palavras subsequentes fica em maiúscula]. Neste exemplo, você alterará os nomes do campo para seguir o estilo camel case. Ao executar esse exemplo, você verá que isso não vai funcionar porque os nomes desejados para os campos entram em conflito com as regras do Go sobre nomes de campo exportados.
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
name string
password string
preferredFish []string
createdAt time.Time
}
func main() {
u := &User{
name: "Sammy the Shark",
password: "fisharegreat",
createdAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Isso apresentará a seguinte saída:
Output{}
Nesta versão, modificamos os nomes dos campos para serem em estilo camel case. Agora, Name
passa a ser name
, Password
a password
e, finalmente, CreatedAt
passa a ser createdAt
. Dentro do corpo de main
, alteramos a instanciação de nossa struct para usar esses novos nomes. Então, enviamos a struct para a função json.MarshalIndent
como antes. A saída, dessa vez é um objeto JSON vazio, {}
.
Os campos em camel case requerem que o primeiro caractere seja minúsculo. Embora JSON não se importe como você nomeia seus campo, o Go se importa, uma vez que indica a visibilidade do campo fora do pacote. Como o pacote encoding/jason
é um pacote separado do pacote main
que estamos usando, devemos usar caixa alta para o primeiro caractere, a fim de torná-lo visível para o encoding/json
. Neste caso, aparentemente, estaríamos com um impasse. Assim, precisamo encontrar uma maneira de transmitir ao codificador JSON como gostaríamos de nomear esse campo.
Você pode modificar o exemplo anterior para os campos sejam exportados com a codificação correta, ou seja, com nomes de campo em camel case, anotando cada campo com um identificador de struct. O identificador de struct que o encoding/json
reconhece tem uma chave do json
e um valor que controla a saída. Colocar a versão dos nomes dos campos em camel case como o valor para a chave json
, fará o codificador usar aquele nome. Este exemplo conserta as duas tentativas anteriores:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string `json:"name"`
Password string `json:"password"`
PreferredFish []string `json:"preferredFish"`
CreatedAt time.Time `json:"createdAt"`
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Isso resultará em:
Output{
"name": "Sammy the Shark",
"password": "fisharegreat",
"preferredFish": null,
"createdAt": "2019-09-23T18:16:17.57739-04:00"
}
Revertemos os nomes de campo para ficarem visíveis para outros pacotes, usando caixa alta nas primeiras letras de seus respectivos nomes. No entanto, adicionamos desta vez os identificadores de struct na forma do json:"name"
, onde "name"
era o nome que queríamos que o json.MarshalIndent
usasse ao imprimir nossa struct como JSON.
Agora, formatamos nosso JSON corretamente. No entanto, note que os campos para alguns valores foram impressos, embora não tivéssemos definido tais valores. O codificador JSON também pode eliminar esses campos, se você quiser.
Mais comumente, queremos suprimir os campos de saída que não estão definidos no JSON. Como todos os tipos em Go tem um “valor zero”, um valor padrão para o qual eles foram definidos, o pacote encoding/json
precisa de informações adicionais para conseguir dizer que um dado campo deverá ser considerado como não definido ao assumir esse valor zero. Dentro de parte do valor de qualquer identificador de struct do json
, você pode acrescentar um sufixo ao nome que deseja para o seu campo, usando o omitempty
. Isso dirá ao codificador JSON que ele deve suprimir a saída desse campo quando ele estiver definido para o valor zero. O exemplo a seguir corrige os exemplos anteriores para não mostrar mais os campos vazios:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string `json:"name"`
Password string `json:"password"`
PreferredFish []string `json:"preferredFish,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Este exemplo irá mostrar o resultado:
Output{
"name": "Sammy the Shark",
"password": "fisharegreat",
"createdAt": "2019-09-23T18:21:53.863846-04:00"
}
Nós modificamos os exemplos anteriores para que o campo PreferredFish
tenha agora o identificador de struct json:"preferredFish,omitempty"
. A presença do acréscimo ,omitempty
faz com que o codificador JSON ignore aquele campo, uma vez que decidimos deixá-lo como não definido. Nos resultados de nossos exemplos anteriores, isso aparecia com valor null
.
Esse resultado ficou muito melhor, mas ainda estamos imprimindo a senha do usuário. O pacote encoding/json
proporciona outra maneira de ignorarmos totalmente os campos privados.
Alguns campos devem ser exportados das structs para que outros pacotes possam interagir corretamente com o tipo. No entanto, esses campos podem ser de natureza confidencial. Assim, em circunstâncias como essas,vamos querer que o codificador JSON ignore totalmente esses campos – mesmo quando ele estiver definido. Para tanto, usamos o valor especial -
como o argumento de valor para um identificador de struct do json:
.
Este exemplo corrige o problema da exposição da senha do usuário.
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string `json:"name"`
Password string `json:"-"`
CreatedAt time.Time `json:"createdAt"`
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Quando executar o exemplo, verá este resultado:
Output{
"name": "Sammy the Shark",
"createdAt": "2019-09-23T16:08:21.124481-04:00"
}
A única coisa que mudamos nesse exemplo - em relação aos exemplos anteriores, foi o campo da senha que agora usa o valor especial "-"
em seu identificador de struct json:
. Constatamos isso no resultado desse exemplo em que o campo password
não está mais presente.
Os recursos ,omitempty
e "-"
do pacote encoding/json
não são padrões. O que um pacote decide fazer com os valores de um identificador de struct depende de sua implementação. Como o pacote encoding/json
faz parte da biblioteca padrão, outros pacotes também implementaram esses recursos da mesma forma somente a título de convenção. No entanto, é importante ler a documentação dos pacotes de terceiros que utilizem identificadores de struct, a fim de aprender quais são compatíveis e quais não são.
Os identificadores de struct proporcionam um meio poderoso para aumentar a funcionalidade do código que funciona com suas structs. Muitos pacotes da biblioteca padrão e de terceiros oferecem maneiras de personalizar sua operação, usando identificadores de struct. Usá-los de maneira eficaz no seu código propicia esse comportamento de personalização e documenta de maneira sucinta como esses campos são usados para conhecimento dos futuros desenvolvedores.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!