Skip to content

Кодирование и декодирование в Go

В современную эпоху интернета наиболее распространёнными независимыми от языка форматами данных являются xml, YAML, json, protobuf. Go также поддерживает операции с этими форматами данных. Ниже приведена сравнительная таблица.

НазваниеXMLYAMLJSONProtocol Buffers
Структура данныхсложнаядовольно простаяпростаядовольно сложная
Способ хранениятексттексттекстбинарный
Размер хранениябольшойсреднийсредниймаленький
Эффективность разборамедленнаясредняясредняябыстрая
Поддержка языковочень многомногоочень многодовольно много
Сложность разработкитрудоёмкаядовольно простаяпростаяпростая
Стоимость обучениянизкаянизкаянизкаянизкая
Область примененияобмен даннымиконфигурационные файлыобмен даннымиобмен данными

TIP

В Go, если вы хотите выполнить сериализацию и десериализацию структуры, поля должны быть экспортируемыми, то есть начинаться с заглавной буквы.

Кроме того, TOML также начинает набирать популярность, его синтаксис похож на улучшенную версию .ini. Заинтересованные могут узнать больше на TOML: Tom's Obvious, Minimal Language.

XML

xml, также известный как eXtensible Markup Language, — это формат для хранения данных, возникший в 1960-х годах и являющийся самым древним из вышеупомянутых форматов данных. Он имеет широкое применение и может использоваться для передачи данных по сети, обмена данными, конфигурационных файлов, хранения данных и т.д. Однако со временем он постепенно заменяется новыми языками разметки.

Сначала определим структуру:

go
type Person struct {
   UserId   string `xml:"id"`
   Username string `xml:"name"`
   Age      int    `xml:"age"`
   Address  string `xml:"address"`
}
go
func Marshal(v any) ([]byte, error) // сериализация xml

func MarshalIndent(v any, prefix, indent string) ([]byte, error) // форматирование

func Unmarshal(data []byte, v any) error // десериализация

Сериализация

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

Вывод:

xml
<Person>
        <id>120</id>
        <name>jack</name>
        <age>18</age>
        <address>usa</address>
</Person>

Десериализация

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

Вывод:

go
{UserId:120 Username:jack Age:18 Address:usa}

Однако традиционный способ разбора xml часто требует создания структур, что очень трудоёмко. Сейчас чаще всего разбираются простые xml-структуры, но при использовании сложных структур это может стать настоящей головной болью. Поэтому большинство разработчиков используют стороннюю библиотеку etree для разбора xml. Заинтересованные могут узнать больше самостоятельно.

YML

Синтаксис YAML похож на другие языки высокого уровня и позволяет просто выражать списки, ассоциативные массивы, скаляры и другие формы данных. Он использует пробелы для отступов и сильно зависит от внешнего вида, что особенно подходит для выражения или редактирования структур данных, различных конфигурационных файлов. YML также существует во многих проектах в виде конфигурационных файлов, его структура содержания более лаконична и наглядна. Официальный Go не предоставляет поддержку YML, поэтому нам нужно использовать сторонний пакет.

powershell
go get github.com/go-yaml/yaml

Основные методы

go
func Marshal(in interface{}) (out []byte, err error) // сериализация

func Unmarshal(in []byte, out interface{}) (err error) // десериализация

Сначала подготовим структуру:

go
type Config struct {
   Database string `yaml:"database"`
   Url      string `yaml:"url"`
   Port     int    `yaml:"port"`
   Username string `yaml:"username"`
   Password string `yaml:"password"`
}

Конфигурационный файл:

yaml
database: mysql
url: 127.0.0.1
port: 3306
username: root
password: 123456

Сериализация

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

Вывод:

yaml
database: oracle
url: localhost
port: 3326
username: root
password: "123456"

Однако, поскольку сам yml имеет строгий синтаксис отступов, проблем с форматированием сериализации не возникает.

Десериализация

go
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. Основные используемые функции:

go
func Marshal(v any) ([]byte, error) // сериализация Go-объекта в JSON-строку

func Unmarshal(data []byte, v any) error // десериализация JSON-строки в Go-объект

Сначала определим структуру:

go
type Person struct {
   UserId   string
   Username string
   Age      int
   Address  string
}

Сериализация

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

Результат:

json
{ "UserId": "120", "Username": "jack", "Age": 18, "Address": "usa" }

Переименование полей

Мы можем использовать теги структур для переименования полей.

go
type Person struct {
   UserId   string `json:"id"`
   Username string `json:"name"`
   Age      int    `json:"age"`
   Address  string `json:"address"`
}

Теперь вывод будет:

json
{ "id": "1202", "name": "jack", "age": 19, "address": "USA" }

Отступы

По умолчанию сериализация не имеет никаких отступов, это сделано для уменьшения затрат пространства при передаче, но это не удобно для человеческого восприятия. В некоторых случаях нам нужно сериализовать данные в читаемый формат. Для этого достаточно использовать другую функцию:

go
func MarshalIndent(v any, prefix, indent string) ([]byte, error)
go
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))
}

Вывод:

json
{
  "id": "1202",
  "name": "jack",
  "age": 19,
  "address": "USA"
}

Десериализация

При десериализации следует учитывать, что если у структуры есть JSON-теги, то в первую очередь следует ориентироваться на них, а не на имена полей структуры.

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

Вывод:

go
{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:

protobuf
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;
}

После генерации файла:

go
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 файла.

Golang by www.golangdev.cn edit