Codificação e Decodificação em Go
Na era digital atual, os formatos de dados independentes de linguagem mais comumente usados são xml, Yaml, json e protobuf. Go também suporta operações relacionadas a esses formatos de dados. Abaixo está uma tabela comparativa.
| Nome | XML | YAML | JSON | Protocol Buffers |
|---|---|---|---|---|
| Estrutura de Dados | Complexa | Mais Simples | Simples | Mais Complexa |
| Método de Armazenamento | Texto | Texto | Texto | Binário |
| Tamanho de Armazenamento | Grande | Médio | Médio | Pequeno |
| Eficiência de Análise | Lenta | Média | Média | Rápida |
| Suporte de Linguagem | Muitos | Muitos | Muitos | Vários |
| Dificuldade de Desenvolvimento | Trabalhosa | Mais Simples | Simples | Simples |
| Custo de Aprendizado | Baixo | Baixo | Baixo | Baixo |
| Escopo de Aplicação | Troca de Dados | Arquivo de Configuração | Troca de Dados | Troca de Dados |
TIP
Em Go, se você deseja serializar e desserializar uma struct, os campos devem ser exportados, ou seja, a primeira letra deve ser maiúscula.
Além disso, o TOML está começando a se tornar popular, com sintaxe semelhante a uma melhoria do .ini. Se estiver interessado, você pode aprender mais em TOML: A Linguagem de Configuração Mínima e Clara de Tom.
XML
xml, também conhecido como eXtensible Markup Language, é um formato usado para armazenar dados, originado na década de 1960, sendo o mais antigo entre os formatos de dados mencionados acima. Seu uso é muito amplo, podendo ser utilizado para transmissão de rede, troca de dados, arquivos de configuração, armazenamento de dados, etc. No entanto, com o passar do tempo, está sendo gradualmente substituído por novas linguagens de marcação.
Primeiro, definimos uma struct:
type Person struct {
UserId string `xml:"id"`
Username string `xml:"name"`
Age int `xml:"age"`
Address string `xml:"address"`
}func Marshal(v any) ([]byte, error) // Serialização XML
func MarshalIndent(v any, prefix, indent string) ([]byte, error) // Formatação
func Unmarshal(data []byte, v any) error // DesserializaçãoSerialização
func main() {
person := Person{
UserId: "120",
Username: "jack",
Age: 18,
Address: "usa",
}
bytes, err := xml.MarshalIndent(person, "", "\t")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(bytes))
}Saída:
<Person>
<id>120</id>
<name>jack</name>
<age>18</age>
<address>usa</address>
</Person>Desserialização
func main() {
var person = Person{
UserId: "",
Username: "",
Age: 0,
Address: "",
}
xmlStr := "<Person> \n <id>120</id> \n <name>jack</name> \n <age>18</age> \n <address>usa</address>\n</Person> "
err := xml.Unmarshal([]byte(xmlStr), &person)
if err != nil {
fmt.Println(err)
return
}
}Saída:
{UserId:120 Username:jack Age:18 Address:usa}No entanto, o método tradicional de análise XML frequentemente requer a criação de structs, o que pode ser muito trabalhoso. Se a análise for de estruturas XML simples, não há problema, mas se forem estruturas complexas, pode se tornar muito complicado. Por isso, a maioria das pessoas usa uma biblioteca de terceiros chamada etree para analisar XML. Se estiver interessado, você pode aprender mais por conta própria: Plugin etree útil para análise de arquivos XML em Go - Juejin (juejin.cn).
YML
A sintaxe do YAML é semelhante a outras linguagens de programação de alto nível e pode expressar facilmente listas, mapas, escalares e outras formas de dados. Usa indentação com espaços em branco e depende muito da aparência, sendo especialmente adequado para expressar ou editar estruturas de dados e vários arquivos de configuração. O YML também existe em muitos projetos na forma de arquivos de configuração, com uma estrutura de conteúdo mais concisa e clara. O Go oficial não fornece suporte para YML, então precisamos usar um pacote de terceiros.
go get github.com/go-yaml/yamlMétodos Principais
func Marshal(in interface{}) (out []byte, err error) // Serialização
func Unmarshal(in []byte, out interface{}) (err error) // DesserializaçãoPrimeiro, preparamos uma struct:
type Config struct {
Database string `yaml:"database"`
Url string `yaml:"url"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}Arquivo de configuração:
database: mysql
url: 127.0.0.1
port: 3306
username: root
password: 123456Serialização
func main() {
config := Config{
Database: "oracle",
Url: "localhost",
Port: 3326,
Username: "root",
Password: "123456",
}
out, err := yaml.Marshal(config)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(out))
}Saída:
database: oracle
url: localhost
port: 3326
username: root
password: "123456"No entanto, como o yml possui uma sintaxe de indentação rigorosa, não há problemas de formatação de serialização.
Desserialização
func main() {
bytes, err := os.ReadFile("./src/config.yml")
if err != nil {
fmt.Println(err)
return
}
var config Config
err = yaml.Unmarshal(bytes, &config)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(config)
}Saída:
{mysql 127.0.0.1 3306 root 123456}JSON
O json é frequentemente usado em comunicações de interfaces de API no estilo Restful. Em comparação com o xml, seu tamanho mais leve e baixo custo de aprendizado o tornaram o formato principal de troca de dados na área web.
Em Go, o pacote encoding/json fornece funções correspondentes para realizar serialização e desserialização JSON. As principais funções usadas são as seguintes:
func Marshal(v any) ([]byte, error) // Serializa um objeto Go para uma string JSON
func Unmarshal(data []byte, v any) error // Desserializa uma string JSON para um objeto GoPrimeiro, definimos uma struct:
type Person struct {
UserId string
Username string
Age int
Address string
}Serialização
func main() {
person := Person{
UserId: "120",
Username: "jack",
Age: 18,
Address: "usa",
}
bytes, err := json.Marshal(person)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(bytes))
}Resultado:
{ "UserId": "120", "Username": "jack", "Age": 18, "Address": "usa" }Renomeação de Campos
Podemos usar tags de struct para alcançar o efeito de renomeação.
type Person struct {
UserId string `json:"id"`
Username string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}Neste caso, a saída será:
{ "id": "1202", "name": "jack", "age": 19, "address": "USA" }Indentação
Por padrão, a serialização não possui nenhuma indentação, isso é feito para reduzir o consumo de espaço durante a transmissão, mas não é favorável à observação humana. Em alguns casos, precisamos serializá-lo em uma forma que os humanos possam observar. Para isso, basta usar uma função diferente:
func MarshalIndent(v any, prefix, indent string) ([]byte, error)func main() {
person := Person{
UserId: "1202",
Username: "jack",
Age: 19,
Address: "USA",
}
bytes, err := json.MarshalIndent(person, "", "\t")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(bytes))
}Saída:
{
"id": "1202",
"name": "jack",
"age": 19,
"address": "USA"
}Desserialização
Ao desserializar, é importante notar que se a struct tiver uma tag json, o nome do campo terá prioridade na tag json, caso contrário, usará o nome da propriedade da struct.
func main() {
person := Person{}
jsonStr := "{\"id\":\"120\",\"name\":\"jack\",\"age\":18,\"address\":\"usa\"}\n"
err := json.Unmarshal([]byte(jsonStr), &person)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v", person)
}Saída:
{UserId:120 Username:jack Age:18 Address:usa}Protocol Buffers
Protocol Buffers é uma linguagem neutra, independente de protocolo e extensível para serialização de dados estruturados, open-source pela Google em 2008. É mais leve que os três mencionados acima e mais rápido na serialização e desserialização, sendo muito utilizado em comunicações RPC. Para mais informações sobre Protobuf, consulte Protobuf.
Instale a dependência:
go get github.com/golang/protobuf/protoArquivo person.proto:
syntax = "proto3";
option go_package = "./;person";
package proto;
enum Gender{
MAIL = 0;
FE_MAIL = 1;
}
message person {
string name = 1;
int32 age = 2;
Gender gender = 3;
}Após gerar o arquivo:
package main
import (
p "GoProject/src/proto"
"fmt"
"github.com/golang/protobuf/proto"
)
func main() {
person := p.Person{
Name: "wyh",
Age: 12,
Gender: p.Gender_FE_MAIL,
}
data, err := proto.Marshal(&person)// Serialização
if err != nil {
fmt.Println(err)
return
}
temp := &p.Person{}
fmt.Println("proto buffer len: ", len(data), "bytes:", data)
err = proto.Unmarshal(data, temp)// Desserialização
if err != nil {
fmt.Println(err)
return
}
fmt.Println(temp)
}Saída:
proto buffer len: 9 bytes: [10 3 119 121 104 16 12 24 1]
name:"wyh" age:12 gender:FE_MAILNormalmente, não fazemos a serialização manualmente. O compilador protoc pode gerar o código-fonte na linguagem correspondente com base no arquivo proto que definimos.
