Кодирование и декодирование в Go
В современную эпоху интернета наиболее распространёнными независимыми от языка форматами данных являются xml, YAML, json, protobuf. Go также поддерживает операции с этими форматами данных. Ниже приведена сравнительная таблица.
| Название | XML | YAML | JSON | Protocol Buffers |
|---|---|---|---|---|
| Структура данных | сложная | довольно простая | простая | довольно сложная |
| Способ хранения | текст | текст | текст | бинарный |
| Размер хранения | большой | средний | средний | маленький |
| Эффективность разбора | медленная | средняя | средняя | быстрая |
| Поддержка языков | очень много | много | очень много | довольно много |
| Сложность разработки | трудоёмкая | довольно простая | простая | простая |
| Стоимость обучения | низкая | низкая | низкая | низкая |
| Область применения | обмен данными | конфигурационные файлы | обмен данными | обмен данными |
TIP
В Go, если вы хотите выполнить сериализацию и десериализацию структуры, поля должны быть экспортируемыми, то есть начинаться с заглавной буквы.
Кроме того, TOML также начинает набирать популярность, его синтаксис похож на улучшенную версию .ini. Заинтересованные могут узнать больше на TOML: Tom's Obvious, Minimal Language.
XML
xml, также известный как eXtensible Markup Language, — это формат для хранения данных, возникший в 1960-х годах и являющийся самым древним из вышеупомянутых форматов данных. Он имеет широкое применение и может использоваться для передачи данных по сети, обмена данными, конфигурационных файлов, хранения данных и т.д. Однако со временем он постепенно заменяется новыми языками разметки.
Сначала определим структуру:
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) // сериализация xml
func MarshalIndent(v any, prefix, indent string) ([]byte, error) // форматирование
func Unmarshal(data []byte, v any) error // десериализацияСериализация
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))
}Вывод:
<Person>
<id>120</id>
<name>jack</name>
<age>18</age>
<address>usa</address>
</Person>Десериализация
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
}
}Вывод:
{UserId:120 Username:jack Age:18 Address:usa}Однако традиционный способ разбора xml часто требует создания структур, что очень трудоёмко. Сейчас чаще всего разбираются простые xml-структуры, но при использовании сложных структур это может стать настоящей головной болью. Поэтому большинство разработчиков используют стороннюю библиотеку etree для разбора xml. Заинтересованные могут узнать больше самостоятельно.
YML
Синтаксис YAML похож на другие языки высокого уровня и позволяет просто выражать списки, ассоциативные массивы, скаляры и другие формы данных. Он использует пробелы для отступов и сильно зависит от внешнего вида, что особенно подходит для выражения или редактирования структур данных, различных конфигурационных файлов. YML также существует во многих проектах в виде конфигурационных файлов, его структура содержания более лаконична и наглядна. Официальный Go не предоставляет поддержку YML, поэтому нам нужно использовать сторонний пакет.
go get github.com/go-yaml/yamlОсновные методы
func Marshal(in interface{}) (out []byte, err error) // сериализация
func Unmarshal(in []byte, out interface{}) (err error) // десериализацияСначала подготовим структуру:
type Config struct {
Database string `yaml:"database"`
Url string `yaml:"url"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}Конфигурационный файл:
database: mysql
url: 127.0.0.1
port: 3306
username: root
password: 123456Сериализация
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))
}Вывод:
database: oracle
url: localhost
port: 3326
username: root
password: "123456"Однако, поскольку сам yml имеет строгий синтаксис отступов, проблем с форматированием сериализации не возникает.
Десериализация
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)
}Вывод:
{mysql 127.0.0.1 3306 root 123456}JSON
json часто используется в коммуникации интерфейсов в стиле Restful. По сравнению с xml, он более лёгкий и имеет низкую стоимость обучения, что сделало его основным форматом обмена данными в веб-разработке.
В Go пакет encoding/json предоставляет соответствующие функции для сериализации и десериализации JSON. Основные используемые функции:
func Marshal(v any) ([]byte, error) // сериализация Go-объекта в JSON-строку
func Unmarshal(data []byte, v any) error // десериализация JSON-строки в Go-объектСначала определим структуру:
type Person struct {
UserId string
Username string
Age int
Address string
}Сериализация
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))
}Результат:
{ "UserId": "120", "Username": "jack", "Age": 18, "Address": "usa" }Переименование полей
Мы можем использовать теги структур для переименования полей.
type Person struct {
UserId string `json:"id"`
Username string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}Теперь вывод будет:
{ "id": "1202", "name": "jack", "age": 19, "address": "USA" }Отступы
По умолчанию сериализация не имеет никаких отступов, это сделано для уменьшения затрат пространства при передаче, но это не удобно для человеческого восприятия. В некоторых случаях нам нужно сериализовать данные в читаемый формат. Для этого достаточно использовать другую функцию:
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))
}Вывод:
{
"id": "1202",
"name": "jack",
"age": 19,
"address": "USA"
}Десериализация
При десериализации следует учитывать, что если у структуры есть JSON-теги, то в первую очередь следует ориентироваться на них, а не на имена полей структуры.
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)
}Вывод:
{UserId:120 Username:jack Age:18 Address:usa}Protocol Buffers
Protocol Buffers — это механизм сериализации структурированных данных, независимый от языка и протокола, с возможностью расширения, открытый Google в 2008 году. По сравнению с тремя вышеупомянутыми форматами, он более лёгкий и обеспечивает более быструю упаковку и распаковку, чаще всего используется в области RPC-коммуникации. Подробнее о Protobuf можно узнать в разделе Protobuf.
Установка зависимости:
go get github.com/golang/protobuf/protoФайл 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;
}После генерации файла:
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) // сериализация
if err != nil {
fmt.Println(err)
return
}
temp := &p.Person{}
fmt.Println("proto buffer len: ", len(data), "bytes:", data)
err = proto.Unmarshal(data, temp) // десериализация
if err != nil {
fmt.Println(err)
return
}
fmt.Println(temp)
}Вывод:
proto buffer len: 9 bytes: [10 3 119 121 104 16 12 24 1]
name:"wyh" age:12 gender:FE_MAILОднако обычно мы не выполняем сериализацию вручную, компилятор protoc может генерировать исходный код на соответствующем языке на основе определённого нами .proto файла.
