Использование конфигурационных файлов в Go: YAML
В продолжение темы про конфигурационные файлы, взглянем на более распространённый формат YAML. YAML (YAML Ain’t Markup Language) - это надмножество над JSON с упрощенным синтаксисом.
Синтаксис
Основные типы
Аналогично TOML, про который я писал ранее, в YAML можно использовать различные типы данных:
# Комментарии начинаются со знака "#"
# Пары ключ-значение разделяются через ":"
# Строки, как значения, могут быть заключены в кавычки (одинарные, двойные),
# но не обязаны
key: значение
another_key: еще одно значение
# Числа можно использовать в экспоненциальной форме
a_number_value: 100
scientific_notation: 1e+12
# Логические значения требуется записывать как true или false.
# 1 и 0 будут считаться в этом контексте как строки.
boolean: true
# Значение может быть null.
null_value: null
# Ключ может содержать пробелы.
key with spaces: value
# Многострочные строки могут быть записаны двумя способами:
# 'literal block' (используя символ '|'),
# или 'folded block' (используя символ '>').
literal_block: |
Весь этот блок текста - значение 'literal_block'.
Переносы строк сохранятся.
Литерал считается значением текущего ключа до тех пор,
пока отступ слева не будет уменьшен до уровня ключа.
Ведущие пробелы в итоговой строке будут опущены.
Все строки, отступ у которых больше обычного (как в этой, например),
сохранят отступы. У этой строки отступ будет равен четырём.
folded_style: >
Этот блок текста - значение 'folded_style'.
Но здесь переносы строк будут заменены пробелами.
Пустые строки, как предыдущая строка, заменяются переносами строк.
Строки с бОльшим отступом сохраняют переносы строк как в литералах -
этот текст будет располагаться на двух строках.
Коллекции
# Вложенность достигается за счёт отступов.
# Этот фрагмент эквивалентен следующему JSON:
# { "a_nested_map: {
# "key": "value",
# "another_key: "Another Value",
# "another_nested_map: {
# "hello": "hello"
# }
# }
# }
a_nested_map:
key: value
another_key: Another Value
another_nested_map:
hello: hello
# Ключом словаря может быть не только строка:
0.25: a float key
# Ключ может быть сложным. Например, многострочными.
# Для этого необходимо использовать символ "?" с последующим пробелом
# для обозначения начала сложного ключа:
? |
Многострочный
ключ из двух строк
: значение, соответствующее сложному ключу
# YAML позволяет создавать словари с составным ключом и значением-перечислением.
# Однако, не все парсеры поддерживают данный синтаксис.
# Пример:
? - Manchester United
- Real Madrid
: [ 2001-01-01, 2002-02-02 ]
# Эквивалентный JSON:
# 'Manchester United,Real Madrid':
# [ Mon Jan 01 2001 04:00:00 GMT+0400 (Russia TZ 3 Standard Time),
# Sat Feb 02 2002 04:00:00 GMT+0400 (Russia TZ 3 Standard Time) ] }
# Массивы или списки:
a_sequence:
- Item 1
- Item 2
- 0.5 # Последовательность может содержать различные типы данных.
- Item 4
- key: value
another_key: another_value
-
- This is a sequence # Последовательность можно содержать подпоследовательности
- inside another sequence
# Так как YAML - это надмножество JSON,
# то можно использовать синтаксис JSON для словарей и массивов:
json_map: {"key": "value"}
json_seq: [3, 2, 1, "takeoff"]
Дополнительные возможности
# YAML поддерживает возможности использования anchor-ов.
# Anchor похож на переменную: оба ключа в примере ниже будут иметь одно значение:
anchored_content: **&anchor_name** Эта строка будет значением для двух ключей.
other_anchor: ***anchor_name**
# Anchor можно использовать для дублирования/наследования свойств:
base: **&base**
name: Все имеют одинаковые имена
foo: &foo
**<<: *base**
age: 10
bar: &bar
**<<: *base**
age: 20
# foo и bar будут иметь ключ "name" со значением "Все имеют одинаковые имена"
# YAML позволяет использовать теги, которые позволяют явно указывать типы:
explicit_string: **!!str** 0.5
# Некоторые парсеры имеют собственные теги.
# Например, тег в Python для комплексных чисел:
python_complex_number: !!python/complex 1+2j
Дополнительные типы данных
# В YAML можно использовать даты в ISO-формате:
datetime: 2001-12-15T02:59:43.1Z
datetime_with_spaces: 2001-12-14 21:59:43.10 -5
date: 2002-12-14
# Тег **!!binary **говорит о том, что следующая за ним строка -
# это закодированные в base64 бинарные данные.
gif_file: !!binary |
R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5
OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+
+f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC
AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=
# YAML содержит тип set:
set:
? item1
? item2
? item3
# Аналогично Python, set - это словарь, в котором значения всех ключей - null:
set2:
item1: null
item2: null
item3: null
Работа с YAML в Go
Все возможности YAML поддерживает только один парсер: go-yaml.
API
Простейший пример выглядит элементарно:
package main
import (
"gopkg.in/yaml.v2"
"log"
"fmt"
"time"
)
var data = `
a: Easy!
b:
c: 2
d: [3, 4]
m: [ "2001-01-01T15:04:05Z", "2002-02-02T15:04:05Z" ]
`
type T struct {
A string
B struct {
RenamedC int `yaml:"c"`
D []int `yaml:",flow"`
}
M []time.Time
}
func main() {
t := T{}
err := yaml.Unmarshal([]byte(data), &t)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- t:\n%v\n\n", t)
}
Из коробки есть возможность распарисить строку в структуру (этим уже не удивишь). Документация утверждает, что библиотека поддерживает anchor-ы и теги. Проверим на практике:
type T1 struct {
A string `yaml:"a"`
B string `yaml:"b"`
I string `yaml:"i"`
Base struct{
Name string
}
Child struct {
Name string
Address string
}
}
func anchorsAndTags() {
var data = `
a: &x Simple Value
b: *x
i: !!binary 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
base: &base
name: Одно имя на всех
child:
<
В общем-то, всё работает как надо:
- после выполнения этой программы в корне проекта появляется файл
img.png
с закодированным изображением, - значения свойств
A
иB
структуры - совпадают и равныSimple Value
, - свойства
Base.Name
равно свойствуChild.Name
, а свойствоChild.Address
равноМой адрес не дом и не улица
.
Скорость на больших конфигурационных файлах
Для анализа возьму файл, который использовал в примерах для
TOML.
Размер полученного файла - примерно 1Мб. Не bigdata, но сейчас этого будет
достаточно. Для профилирования использую библиотеку
[stopwatch](https://github.com/fatih/stopwatch)
, которую так же использовал
в предыдущий раз для профилирования парсинга TOML.
sw := stopwatch.New()
sw.Start(0)
singleYaml()
log.Printf("Yaml simple decode: %s", sw.ElapsedTime().String())
sw.Reset()
sw.Start(0)
for i := 0; i < 10; i++ {
largeYaml()
}
log.Printf("Large yaml 10 times: %s", sw.ElapsedTime().String())
Замерил скорость работы для маленького и для большого YAML-файлов. Результаты получились очень привлекательные: 3.0017ms для первого и 186.0448ms для второго случаев. Для сравнения, при разборе TOML лучший результат для аналогичного файла был 2.49 секунды.
Итого
YAML - более распространённый на данный момент формат конфигурационных файлов. Возможно, это, в том числе, является причиной более производительной работы. Так же радуют интересные фичи YAML, anchor-ы и теги, позволяющие удобнее организовывать данные. Все примеры можно найти в репозитории на bitbucket.