One place for hosting & domains

      Struct

      Como usar tags struct em Go


      Introdução

      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.

      Qual é a aparência de uma tag 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:

      Output

      Hi! 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.

      Codificando o 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.

      Usando identificadores de struct para controlar a codificação.

      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.

      Removendo os campos vazios do JSON

      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.

      Ignorando 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.

      Conclusã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.



      Source link

      How To Use Struct Tags in Go


      Introduction

      Structures, or structs, are used to collect multiple pieces of information together in one unit. These collections of information are used to describe higher-level concepts, such as an Address composed of a Street, City, State, and PostalCode. When you read this information from systems such as databases, or APIs, you can use struct tags to control how this information is assigned to the fields of a struct. Struct tags are small pieces of metadata attached to fields of a struct that provide instructions to other Go code that works with the struct.

      What Does a Struct Tag Look Like?

      Go struct tags are annotations that appear after the type in a Go struct declaration. Each tag is composed of short strings associated with some corresponding value.

      A struct tag looks like this, with the tag offset with backtick ` characters:

      type User struct {
          Name string `example:"name"`
      }
      

      Other Go code is then capable of examining these structs and extracting the values assigned to specific keys it requests. Struct tags have no effect on the operation of your code without some other code that examines them.

      Try this example to see what struct tags look like, and that without code from another package, they will have no effect.

      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)
      }
      

      This will output:

      Output

      Hi! My name is Sammy

      This example defines a User type with a Name field. The Name field has been given a struct tag of example:"name". We would refer to this specific tag in conversation as the “example struct tag” because it uses the word “example” as its key. The example struct tag has the value "name" for the Name field. On the User type, we also define the String() method required by the fmt.Stringer interface. This will be called automatically when we pass the type to fmt.Println and gives us a chance to produce a nicely formatted version of our struct.

      Within the body of main, we create a new instance of our User type and pass it to fmt.Println. Even though the struct had a struct tag present, we see that it has no effect on the operation of this Go code. It will behave exactly the same if the struct tag were not present.

      To use struct tags to accomplish something, other Go code must be written to examine structs at runtime. The standard library has packages that use struct tags as part of their operation. The most popular of these is the encoding/json package.

      Encoding JSON

      JavaScript Object Notation (JSON) is a textual format for encoding collections of data organized under different string keys. It’s commonly used to communicate data between different programs as the format is simple enough that libraries exist to decode it in many different languages. The following is an example of JSON:

      {
        "language": "Go",
        "mascot": "Gopher"
      }
      

      This JSON object contains two keys, language and mascot. Following these keys are the associated values. Here the language key has a value of Go and mascot is assigned the value Gopher.

      The JSON encoder in the standard library makes use of struct tags as annotations indicating to the encoder how you would like to name your fields in the JSON output. These JSON encoding and decoding mechanisms can be found in the encoding/json package.

      Try this example to see how JSON is encoded without struct tags:

      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))
      }
      

      This will print the following output:

      Output

      { "Name": "Sammy the Shark", "Password": "fisharegreat", "CreatedAt": "2019-09-23T15:50:01.203059-04:00" }

      We defined a struct describing a user with fields including their name, password, and the time the user was created. Within the main function, we create an instance of this user by supplying values for all fields except PreferredFish (Sammy likes all fish). We then passed the instance of User to the json.MarshalIndent function. This is used so we can more easily see the JSON output without using an external formatting tool. This call could be replaced with json.Marshal(u) to receive JSON without any additional whitespace. The two additional arguments to json.MarshalIndent control the prefix to the output (which we have omitted with the empty string), and the characters to use for indenting, which here are two space characters. Any errors produced from json.MarshalIndent are logged and the program terminates using os.Exit(1). Finally, we cast the []byte returned from json.MarshalIndent to a string and hand the resulting string to fmt.Println for printing on the terminal.

      The fields of the struct appear exactly as we named them. This is not the typical JSON style that you may expect, which uses camel casing for names of fields. You’ll change the names of the field to follow camel case style in this next example. As you’ll see when you run this example, this won’t work because the desired field names conflict with Go’s rules about exported field names.

      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))
      }
      

      This will present the following output:

      Output

      {}

      In this version, we’ve altered the names of the fields to be camel cased. Now Name is name, Password is password, and finally CreatedAt is createdAt. Within the body of main we’ve changed the instantiation of our struct to use these new names. We then pass the struct to the json.MarshalIndent function as before. The output, this time is an empty JSON object, {}.

      Camel casing fields properly requires that the first character be lower-cased. While JSON doesn’t care how you name your fields, Go does, as it indicates the visibility of the field outside of the package. Since the encoding/json package is a separate package from the main package we’re using, we must uppercase the first character in order to make it visible to encoding/json. It would seem that we’re at an impasse, and we need some way to convey to the JSON encoder what we would like this field to be named.

      Using Struct Tags to Control Encoding

      You can modify the previous example to have exported fields that are properly encoded with camel-cased field names by annotating each field with a struct tag. The struct tag that encoding/json recognizes has a key of json and a value that controls the output. By placing the camel-cased version of the field names as the value to the json key, the encoder will use that name instead. This example fixes the previous two attempts:

      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))
      }
      

      This will output:

      Output

      { "name": "Sammy the Shark", "password": "fisharegreat", "preferredFish": null, "createdAt": "2019-09-23T18:16:17.57739-04:00" }

      We’ve changed the field names back to be visible to other packages by capitalizing the first letters of their names. However, this time we’ve added struct tags in the form of json:"name", where "name" was the name we wanted json.MarshalIndent to use when printing our struct as JSON.

      We’ve now successfully formatted our JSON correctly. Notice, however, that the fields for some values were printed even though we did not set those values. The JSON encoder can eliminate these fields as well, if you like.

      Removing Empty JSON Fields

      Most commonly, we want to suppress outputting fields that are unset in JSON. Since all types in Go have a “zero value,” some default value that they are set to, the encoding/json package needs additional information to be able to tell that some field should be considered unset when it assumes this zero value. Within the value part of any json struct tag, you can suffix the desired name of your field with ,omitempty to tell the JSON encoder to suppress the output of this field when the field is set to the zero value. The following example fixes the previous examples to no longer output empty fields:

      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))
      }
      

      This example will output:

      Output

      { "name": "Sammy the Shark", "password": "fisharegreat", "createdAt": "2019-09-23T18:21:53.863846-04:00" }

      We’ve modified the previous examples so that the PreferredFish field now has the struct tag json:"preferredFish,omitempty". The presence of the ,omitempty augmentation causes the JSON encoder to skip that field, since we decided to leave it unset. This had the value null in our previous examples’ outputs.

      This output is looking much better, but we’re still printing out the user’s password. The encoding/json package provides another way for us to ignore private fields entirely.

      Ignoring Private Fields

      Some fields must be exported from structs so that other packages can correctly interact with the type. However, the nature of these fields may be sensitive, so in these circumstances, we would like the JSON encoder to ignore the field entirely—even when it is set. This is done using the special value - as the value argument to a json: struct tag.

      This example fixes the issue of exposing the user’s password.

      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))
      }
      

      When you run this example, you’ll see this output:

      Output

      { "name": "Sammy the Shark", "createdAt": "2019-09-23T16:08:21.124481-04:00" }

      The only thing we’ve changed in this example from previous ones is that the password field now uses the special "-" value for its json: struct tag. We see that in the output from this example that the password field is no longer present.

      These features of the encoding/json package, ,omitempty and "-", are not standards. What a package decides to do with values of a struct tag depends on its implementation. Because the encoding/json package is part of the standard library, other packages have also implemented these features in the same way as a matter of convention. However, it’s important to read the documentation for any third-party package that uses struct tags to learn what is supported and what is not.

      Conclusion

      Struct tags offer a powerful means to augment the functionality of code that works with your structs. Many standard library and third-party packages offer ways to customize their operation through the use of struct tags. Using them effectively in your code provides both this customization behavior and succinctly documents how these fields are used to future developers.



      Source link