AccountBalance и CurrencyAmount, которые содержат информацию о балансе пользователя. В текущей версии у этих типов нет структурных тегов:// AccountBalance содержит анонимизированную информацию о балансе аккаунта.
type AccountBalance struct {
    AccountIdHash []byte           // хеш идентификатора пользователя
    Amounts       []CurrencyAmount // балансы в разных валютах
    IsBlocked     bool             // флаг блокировки аккаунта
}
// CurrencyAmount содержит нормированное представление баланса.
// 1.50$ -> { Amount: 150, Decimals: 2, Symbol: "$" }.
type CurrencyAmount struct {
    Amount   int64  // нормальное значение баланса
    Decimals int8   // количество цифр после запятой
    Symbol   string // идентификатор валюты
} go-yaml использует рефлексию для получения метаданных о типе. Чтобы задать опции для поля структуры, применяют структурные теги yaml:type MyType struct {
    ID    int      `yaml:"id"`
    Title string   `yaml:"title,omitempty"`
    List  []string `yaml:"name,flow"`
} -. omitempty — этот флаг исключает поле из YAML-представления, если поле имеет нулевое значение (аналогично JSON, XML);flow — при использовании этого флага для структур, слайсов и мап данные отобразятся в однострочном формате;inline — при использовании этого флага для структур все их поля отобразятся как поля родительского объекта.flow и inline влияют на конечный результат, приведём такой пример:  type Leaf struct {
    ID   int    `yaml:"id"`
    Name string `yaml:"name"`
}
type Tree struct {
    Owner Leaf `yaml:"owner"`
    Left  Leaf `yaml:"left,flow"`
    Right Leaf `yaml:"right,inline"`
}
var tree = Tree{
    Owner: Leaf{1, "Owner"},
    Left:  Leaf{2, "Left child"},
    Right: Leaf{3, "Right child"},
} tree в YAML-формате будет выглядеть так:owner:
  id: 1
  name: Owner
left: {id: 2, name: Left child}
id: 3
name: Right child Marshal() и Unmarshal(). Также есть типы Encoder и Decoder с соответствующими методами Encode() и Decode().yaml для типов AccountBalance, CurrencyAmount и преобразуем переменную balance в YAML-формат:package main
import (
    "fmt"
    "gopkg.in/yaml.v3"
)
type (
    AccountBalance struct {
        AccountIdHash []byte           `yaml:"account_id_hash,flow"`
        Amounts       []CurrencyAmount `yaml:"amounts,omitempty"`
        IsBlocked     bool             `yaml:"is_blocked"`
    }
    CurrencyAmount struct {
        Amount   int64  `yaml:"amount"`
        Decimals int8   `yaml:"decimals"`
        Symbol   string `yaml:"symbol"`
    }
)
func main() {
    balance := AccountBalance{
        AccountIdHash: []byte{0x10, 0x20, 0x0A, 0x0B},
        Amounts: []CurrencyAmount{
            {Amount: 1000000, Decimals: 2, Symbol: "RUB"},
            {Amount: 2510, Decimals: 2, Symbol: "USD"},
        },
        IsBlocked: true,
    }
    // преобразуем значение переменной balance в YAML-формат
    out, err := yaml.Marshal(balance)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(out))
} account_id_hash: [16, 32, 10, 11]
amounts:
- amount: 1000000
  decimals: 2
  symbol: RUB
- amount: 2510
  decimals: 2
  symbol: USD
is_blocked: true toml, comment и commented:toml:"name,[omitempty]" — определяет имя поля в TOML-представлении аналогично JSON, XML, YAML;comment:"Комментарий" — добавляет комментарий с указанной строкой выше TOML-поля;commented:"true" — комментирует поле в TOML-представлении.github.com/pelletier/go-toml.toml-теги.toml.Marshal(balance).package main
import (
    "fmt"
    "github.com/pelletier/go-toml"
)
type (
    AccountBalance struct {
        AccountIdHash []byte           `toml:"account_id_hash"`
        Amounts       []CurrencyAmount `toml:"amounts,omitempty"`
        IsBlocked     bool             `toml:"is_blocked" comment:"Deprecated" commented:"true"`
    }
    CurrencyAmount struct {
        Amount   int64  `toml:"amount"`
        Decimals int8   `toml:"decimals"`
        Symbol   string `toml:"symbol"`
    }
)
func main() {
    balance := AccountBalance{
        AccountIdHash: []byte{0x10, 0x20, 0x0A, 0x0B},
        Amounts: []CurrencyAmount{
            {Amount: 1000000, Decimals: 2, Symbol: "RUB"},
            {Amount: 2510, Decimals: 2, Symbol: "USD"},
        },
        IsBlocked: true,
    }
    // преобразуем значение переменной balance в TOML-формат
    out, err := toml.Marshal(balance)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(out))
} account_id_hash = [16, 32, 10, 11]
# Deprecated
# is_blocked = true
[[amounts]]
  amount = 1000000
  decimals = 2
  symbol = "RUB"
[[amounts]]
  amount = 2510
  decimals = 2
  symbol = "USD" Marshal() и Unmarshal().easyjson (${GOPATH}/bin/easyjson), которая генерирует код сериализации и десериализации конкретных типов:go get github.com/mailru/easyjson
go install github.com/mailru/easyjson/...@latest easyjson <file.go> с полным или относительным путём к файлу со структурами. В директории с исходным файлом будет создан file_easyjson.go c нужными для конвертации методами, включая MarshalJSON() и UnmarshalJSON(). reflect. easyjson не работает для файлов в пакетах main.easyjson может принимать дополнительные параметры командной строки, например:easyjson -all -snake_case myjson.go -all генерирует код для всех типов в указанном файле;-snake_case форматирует имена JSON-полей в свой формат.json.Marshaler и json.Unmarshaler. Объект можно использовать со стандартной библиотекой (с функциями json.MarshalIndent(), json.Unmarshal() и другими). Однако разработчик easyjson не советует этого делать, так как easyjson.Marshal() отработает быстрее, чем json.Marshal().easyjson:// myeasyjson/myjson/myjson.go
// не забудьте запустить easyjson -all myjson.go
package myjson
type (
    AccountBalance struct {
        AccountIdHash []byte           `json:"account_id_hash,flow"`
        Amounts       []CurrencyAmount `json:"amounts,omitempty"`
        IsBlocked     bool             `json:"is_blocked"`
    }
    CurrencyAmount struct {
        Amount   int64  `json:"amount"`
        Decimals int8   `json:"decimals"`
        Symbol   string `json:"symbol"`
    }
)
// myeasyjson/main.go
package main
import (
    "fmt"
    "myeasyjson/myjson"
    "github.com/mailru/easyjson"
)
func main() {
    balance := myjson.AccountBalance{
        AccountIdHash: []byte{0x10, 0x20, 0x0A, 0x0B},
        Amounts: []myjson.CurrencyAmount{
            {Amount: 1000000, Decimals: 2, Symbol: "RUB"},
            {Amount: 2510, Decimals: 2, Symbol: "USD"},
        },
        IsBlocked: true,
    }
  // преобразуем значение переменной balance в JSON-формат
    out, err := easyjson.Marshal(balance)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(out))
} {"account_id_hash":"ECAKCw==","amounts":[{"amount":1000000,"decimals":2,"symbol":"RUB"},{"amount":2510,"decimals":2,"symbol":"USD"}],"is_blocked":true} easyjson можно воспользоваться директивой //go:generate и указать там запуск easyjson с нужными параметрами:package myjson
//go:generate easyjson -all myjson.go
type AccountBalance struct {
        AccountIdHash []byte           `json:"account_id_hash,flow"`
        Amounts       []CurrencyAmount `json:"amounts,omitempty"`
        IsBlocked     bool             `json:"is_blocked"`
}
//... go generate, которая и вызовет указанную в директиве программу.easyjson подходит для случаев, когда схема данных известна заранее и не нужно производить динамическую десериализацию. Если же это необходимо, можно ограничить применение easyjson для статических типов, а для динамической части реализовать JSON-интерфейсы самостоятельно.go install github.com/tinylib/msgp@latest. //go:generate msgp перед объявлением сериализуемых типов и запустить go generate. Опционально можно указать структурные теги msg:type S struct {
    Name    string `msg:"name"` // переопределение имени поля в представлении
    Comment string `msg:"-"`    // игнорирование поля
} msgp создаёт для типов файл <filename>_gen.go с реализацией следующих методов: MarshalMsg(), UnmarshalMsg(), EncodeMsg(), DecodeMsg(), Msgsize(). Рассмотрим подробнее методы для сериализации и десериализации объектов.MarshalMsg([]byte)([]byte, error) MarshalMsg() принимает на вход слайс байт и использует его для хранения результата. Если длины буфера недостаточно, создаётся новый слайс (в примере для создания нового буфера данных передаётся nil).UnmarshalMsg([]byte)([]byte, error) UnmarshalMsg() принимает на вход слайс байт, содержащий сериализованные данные. Помимо ошибки, функция возвращает слайс байт с тем, что осталось после десериализации, то есть неиспользованные данные.AccountBalance и CurrencyAmount, то сериализация и десериализация будут выглядеть так:package main
import "fmt"
//go:generate msgp
type AccountBalance struct {
    AccountIdHash []byte           `msg:"account_id_hash"`
    Amounts       []CurrencyAmount `msg:"amounts"`
    IsBlocked     bool             `msg:"is_blocked"`
}
type CurrencyAmount struct {
    Amount   int64 // здесь не будем использовать структурные теги
    Decimals int8
    Symbol   string
}
func main() {
    balance := AccountBalance{
        AccountIdHash: []byte{0x10, 0x20, 0x0A, 0x0B},
        Amounts: []CurrencyAmount{
            {Amount: 1000000, Decimals: 2, Symbol: "RUB"},
            {Amount: 2510, Decimals: 2, Symbol: "USD"},
        },
        IsBlocked: true,
    }
    // сериализуем значение переменной balance
    msgpBz, err := balance.MarshalMsg(nil)
    if err != nil {
        panic(err)
    }
    var balanceCopy AccountBalance
    // декодируем данные в переменную типа AccountBalance
    if _, err := balanceCopy.UnmarshalMsg(msgpBz); err != nil {
        panic(err)
    }
    // для сравнения выведем оригинальное и полученное значения
    fmt.Printf("balanceInit: %+v\n", balance)
    fmt.Printf("balanceCopy: %+v\n", balanceCopy)
} balanceInit: {AccountIdHash:[16 32 10 11] Amounts:[{Amount:1000000 Decimals:2 Symbol:RUB} {Amount:2510 Decimals:2 Symbol:USD}] IsBlocked:true}
balanceCopy: {AccountIdHash:[16 32 10 11] Amounts:[{Amount:1000000 Decimals:2 Symbol:RUB} {Amount:2510 Decimals:2 Symbol:USD}] IsBlocked:true} msgp-реализации протокола MessagePack.protoc (вот инструкция по установке) из proto-файлов можно сгенерировать специфичный для языка код работы с сериализуемыми типами.protoc помогает генерировать связующий код для разных языков программирования (C++, JavaScript, Go, Rust и других). Чтобы protoc генерировал код на языке Go, нужно ещё установить соответствующий плагин: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest.protoc-утилиты важно знать структуру файлов проекта, а также его Go-путь. Допустим, есть Go-проект — protobuf с такой структурой каталога:protobuf
│   main.go // сама программа
│
└───proto // все Protobuf-файлы
│     │   acc_balance.proto // proto-описание типов AccountBalance и CurrencyAmount proto3-спецификация (файл proto/acc_balance.proto):syntax = "proto3";
// имя proto-пакета и версия
// версию указывать необязательно, это общепринятый подход 
// для версионирования спецификации
package account.v1beta1;
// опция задаёт пакет для генерируемого файла
// файл будет создаваться в родительской директории с именем пакета main
option go_package = "./main";
// описание типа AccountBalance
message AccountBalance {
  bytes account_id_hash = 1;                // Go: []byte
  bool is_blocked = 2;                      // Go: bool
  repeated CurrencyAmount amounts = 3;      // Go: []CurrencyAmount
}
// описание типа CurrencyAmount
message CurrencyAmount {
  int64  amount = 1;   // Go: int64
  int32  decimals = 2; // Go: int32 (int8 не определён спецификацией proto3)
  string symbol = 3;   // Go: string
} protobuf и запустить protoc с нужными параметрами:protoc --proto_path=proto --go_opt=paths=source_relative --go_out=. proto/acc_balance.proto --proto_path — путь до папки со всеми proto-файлами проекта.
Proto-файл может импортировать другие proto-файлы, поэтому указываем для protoc, где искать локальные зависимости.--go_opt — специфичные для Go опции.
Опция paths=source_relative заставляет protoc использовать относительные пути для генерируемых файлов.--go_out — путь до папки, куда protoc будет записывать генерируемые .go-файлы.
Пишем рабочую папку проекта, так как при указании опции paths=source_relative утилита сама создаст нужные папки.proto/acc_balance.proto — путь до исходного proto-файла.
Можно передавать несколько файлов, разделённых пробелом../acc_balance.pb.go с нужными Go-типами.protoc не создаёт методы Marshal() и Unmarshal() для Go-типов. Нужно добавить в зависимости проекта библиотеку protobuf-go, которая определяет функции для работы с Protobuf. package main
import (
    "fmt"
    "github.com/golang/protobuf/proto"
)
func main() {
    balance := AccountBalance{
        AccountIdHash: []byte{0x10, 0x20, 0x0A, 0x0B},
        Amounts: []*CurrencyAmount{
            {Amount: 1000000, Decimals: 2, Symbol: "RUB"},
            {Amount: 2510, Decimals: 2, Symbol: "USD"},
        },
        IsBlocked: true,
    }
    // сериализуем значение переменной balance
    protoBz, err := proto.Marshal(&balance)
    if err != nil {
        panic(err)
    }
    var balanceCopy AccountBalance
    // декодируем данные в новую переменную
    if err := proto.Unmarshal(protoBz, &balanceCopy); err != nil {
        panic(err)
    }
    // визуально сравниваем значения переменных
    fmt.Printf("balanceInit: %s\n", balance.String())
    fmt.Printf("balanceCopy: %s\n", balanceCopy.String())
} balanceInit: account_id_hash:"\x10 \n\x0b" is_blocked:true amounts:{amount:1000000 decimals:2 symbol:"RUB"} amounts:{amount:2510 decimals:2 symbol:"USD"}
balanceCopy: account_id_hash:"\x10 \n\x0b" is_blocked:true amounts:{amount:1000000 decimals:2 symbol:"RUB"} amounts:{amount:2510 decimals:2 symbol:"USD"} AccountBalanceAccountBalance в сгенерированном файле выглядит так:type AccountBalance struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields
    AccountIdHash []byte                 `protobuf:"bytes,1,opt,name=account_id_hash,json=accountIdHash,proto3" json:"account_id_hash,omitempty"` // Go: []byte
    IsBlocked     bool                   `protobuf:"varint,2,opt,name=is_blocked,json=isBlocked,proto3" json:"is_blocked,omitempty"`              // Go: bool
    Amounts       []*CurrencyAmount      `protobuf:"bytes,3,rep,name=amounts,proto3" json:"amounts,omitempty"`                                    // Go: []CurrencyAmount
} Amounts тип []*CurrencyAmount, то есть слайс указателей на CurrencyAmount. Любые вложенные структуры определяются Protobuf как указатели, поэтому при инициализации экземпляра созданы указатели на CurrencyAmount. В примере слайс указателей не согласуется с логикой приложения (эти объекты не должны быть nullable), но это стандартное поведение protoc изменить невозможно.protoc создаёт для Go-типов метод String(), поэтому в консоль объекты выводим через %s, что делает консольный вывод более читаемым по сравнению с %+v.yaml, toml.easyjson — пакет для JSON-сериализации, требующий предварительной кодогенерации. Работает гораздо быстрее сериализатора стандартной библиотеки, потому что не использует рефлексию. Зато использует такие же структурные теги и совместим с пакетом encoding/json.msg и требует для структур кодогенерации необходимых методов.protoc на основе описания структур данных и методов в proto-файле.easyjson), другие добавляют недостающую (Protobuf, MessagePack).easyjson и другим).