YAML та TOML¶
Стандартна бібліотека має JSON і XML, але не YAML чи TOML. Обидва
поширені в конфігурації, і обидва працюють так, як навчала стаття про
кодування JSON —
теги структур та Marshal/Unmarshal.
Модулі:
gopkg.in/yaml.v3таgithub.com/pelletier/go-toml/v2.
Кілька просторів тегів на одній структурі¶
Тип, що читається з файлу конфігурації й віддається як JSON, несе обидва теги — механіка зі статті про структури, де кожна бібліотека читає лише свій власний ключ:
type Server struct {
Host string `yaml:"host" json:"host" toml:"host"`
Port int `yaml:"port" json:"port" toml:"port"`
Timeout time.Duration `yaml:"timeout" json:"timeout" toml:"timeout"`
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`
Secret string `yaml:"-" json:"-"`
}
omitempty і - означають те саме, що й у encoding/json. Ключ за
замовчуванням, однак, відрізняється: encoding/json використовує ім'я
поля Go, тоді як yaml.v3 переводить його в нижній регістр. Пишіть тег
явно — і питання не виникає.
YAML¶
src := `
server:
host: example.com
port: 8080
timeout: 30s
tags: [a, b]
extra:
k: v
`
var d Doc
err := yaml.Unmarshal([]byte(src), &d)
// {Server:{Host:example.com Port:8080 Timeout:30s Tags:[a b]} Extra:map[k:v]}
yaml.v3 парсить time.Duration з рядка. 30s стає справжньою
тривалістю без жодного власного анмаршалера — вартий знання факт, бо
саме через нього структури конфігурації можуть використовувати
правильні типи.
Маршалінг відступає чотирма пробілами й бере в лапки лише за потреби:
Відхиляйте невідомі поля¶
За замовчуванням нерозпізнаний ключ мовчки ігнорується, тож одруківка
у файлі конфігурації нічого не робить, а значення за замовчуванням
залишається. KnownFields перетворює це на помилку:
dec := yaml.NewDecoder(r)
dec.KnownFields(true)
err := dec.Decode(&d)
// yaml: unmarshal errors:
// line 2: field nope not found in type main.Server
Вмикайте це для всього, що редагує людина. Одруківка tiemout, після
якої залишається значення за замовчуванням — це поганий вечір.
Помилки типів уже повідомляють рядок:
yaml.Unmarshal([]byte("server:\n port: notanumber\n"), &d)
// yaml: unmarshal errors:
// line 2: cannot unmarshal !!str `notanumber` into int
У YAML є гострі кути¶
Вони належать формату, не бібліотеці:
- Відступ значущий, а таб — синтаксична помилка.
- Значення без лапок вгадуються.
yes,no,on,offстають булевими; провідний нуль може стати вісімковим числом. Беріть у лапки все, що має залишитись рядком — особливо номери версій і коди країн. - Якорі та псевдоніми (
&name,*name) існують і розгортаються при читанні, що іноді корисно, а частіше дивує. - Ненадійний YAML небезпечно парсити в
any. Надавайте перевагу структурі й встановлюйте ліміт розміру вхідних даних.
TOML¶
Плоскіший, без значущих пробілів, однозначні скаляри:
Помилки називають поле й типи, що беруть участь:
toml.Unmarshal([]byte("[server]\nport = \"nope\"\n"), &d)
// toml: cannot decode TOML string into struct field main.Server.Port of type int
TOML не парсить тривалість¶
Це відмінність, яка спіймає вас під час перенесення структури між двома форматами:
// timeout = "5s"
// toml: cannot decode TOML string into struct field
// main.Server.Timeout of type time.Duration
А маршалінг тривалості видає сире ціле число наносекунд замість 5s.
Якщо структура спільна для YAML і TOML, або оголосіть поле як string
і парсіть самостійно через time.ParseDuration, або дайте типу власні
UnmarshalText/MarshalText. Обидві бібліотеки викликають ці методи,
якщо тип їх має, тож другий варіант виглядає так:
type Duration struct{ time.Duration }
func (d *Duration) UnmarshalText(b []byte) error {
v, err := time.ParseDuration(string(b))
d.Duration = v
return err
}
func (d Duration) MarshalText() ([]byte, error) {
return []byte(d.String()), nil
}
Якщо в структурі є поле Timeout Duration, то timeout = "5s"
декодується в TOML, а timeout: 5s — у YAML, і маршалінг записує назад
5s замість наносекунд.
У TOML є власні дати й час, тоді як YAML лише наближено це підтримує.
Що вибрати¶
| Використання | Формат |
|---|---|
| Kubernetes, CI, усе в цій екосистемі | YAML — реального вибору немає |
| власний файл конфігурації інструменту | TOML — менше способів помилитися |
| API | JSON — він у стандартній бібліотеці |
Перевага TOML у тому, що в ньому немає неоднозначних скалярів і правил відступу, тож файл, відредагований вручну, важче зламати. Перевага YAML у тому, що все інше вже говорить цією мовою.
Що б ви не обрали: декодуйте в структуру, а не в
map[string]any, і валідуйте після декодування. Обидва формати
охоче віддадуть вам синтаксично правильний документ, що нічого не
означає.
З досвіду Python:
yaml.Unmarshal— цеyaml.safe_loadу dataclass, і тут немає небезпечного завантажувача, до якого можна випадково потягнутися.KnownFields(true)— та сама строгість, яку даєextra="forbid"у pydantic, і тут вона теж вимкнена за замовчуванням.
Швидка довідка¶
| Задача | Форма |
|---|---|
| декодувати YAML | yaml.Unmarshal(b, &v) |
| закодувати YAML | yaml.Marshal(v) — відступ у чотири пробіли |
| відхилити одруківки | dec := yaml.NewDecoder(r); dec.KnownFields(true) |
| тривалості в YAML | працюють нативно з 30s |
| декодувати TOML | toml.Unmarshal(b, &v) |
| тривалості в TOML | не підтримуються — використовуйте рядок або UnmarshalText |
| теги | yaml:"name,omitempty", toml:"name", - щоб пропустити |
| кілька форматів | накладіть теги на одне поле |
| завжди | декодуйте в структуру, потім валідуйте |