Подводные камни YAML
https://t.me/humane_analyst
YAML позиционируется как простой и удобный формат, однако эти его преимущества имеют обратную сторону: пользуясь его лаконичным синтаксисом вы можете столкнуться с рядом неожиданностей. Рассмотрим их подробнее.
Отступы в блочной структуре
Начну с самого простого, хотя и важного: в YAML отступы имеют значение. Все элементы структуры должны иметь одинаковый уровень отступа от своего родителя. Вкрался лишний пробел — вот это ошибка. В общем, в данном случае это не вопрос красоты и удобочитаемости.
Необязательность кавычек в ключах
YAML «умный», поэтому для него не обязательно заключать имя ключа в кавычки. В такой ситуации часто возникает соблазн сэкономить несколько байт памяти. Если мы разрабатываем конфиг-файлы, то рано или поздно у нас могут появиться ключи типа "v2.1" или, что более лаконично, — "2.1". Всё будет работать, однако не всегда так, как задумано. Вот пример.
Допустим, вы описываете конфигурацию, где в зависимости от версии клиентского приложения должно подбираться то или иное значение. Алгоритм: перебрать значения ключей в порядке убывания и выбрать первый попавшийся ключ, который меньше версии клиентского приложения.
Если у вас в конфиге будут ключи 2.3, 2.10, 2.12, а у клиента версия приложения 2.11, то вы можете ожидать, что по алгоритму выберется 2.10, но на деле выберется 2.3. Почему? Секрет в том, что в YAML поддерживает разные типы значений в ключе. А раз так, то ключи данного вида парсер проинтерпретирует как числовые (в данном случае это вещественные числа), а по правилам математики ноль в конце дробной части не является значимым. Как результат, ключ 2.10 будет считаться ключом 2.1, наша отсортированная последовательность примет вид: 2.1, 2.3, 2.12, алгоритм остановится на 2.3.
Дополню, что если в рассмотренном примере у нас уже присутствовал бы ключ для версии 2.1, то интерпретация 2.10 как 2.1 вообще привела бы к непредсказуемому результату. Тут многое зависит от конкретного парсера YAML, что добавляет дополнительную неопределённость.
Решение: в ситуации, когда ключ похож на число, но нужно, чтобы это была строка, следует использовать кавычки.
Добавлю, что если вы хотите сказать, что у вас версии приложений имеют 3 секции (например, 2.3.10) и это для вас неактуально, то не спешите. Рано или поздно может что-то измениться и у вас может появиться ключ, состоящий из двух секций. В тот момент вы вряд ли вспомните про то, что нужно ключи с версиями обернуть в кавычки.
Необязательность кавычек в значениях
Здесь ситуация аналогична предыдущей, но я бы хотел рассмотреть другой пример. Допустим, мы хотим хранить в YAML-файле регулярное выражение для проверки телефонного номера на соответствие некому формату.
Если мы просто напишем "phone_regex: \(?\d{3}\)?[-\s]?\d{3}[-\s]?\d{4}", то может случиться неприятность, поскольку некоторые символы внутри «регулярки» являются спецсимволами YAML. К примеру, парсер вполне может попытаться интерпретировать квадратные скобки как массив.
Также доводилось встречать описание проблемы интерпретации значений, содержащих другие спецсимволы (например, значение *.php приведёт к ошибке, поскольку звёздочка используется в YAML для ссылок по якорям).
Решение: можно все спорные символы экранировать обратными слэшами, а можно взять всё значение целиком в кавычки.
Такой разный булев тип
Разработчики YAML не искали лёгких путей и помимо true и false предусмотрели несколько альтернативных способов указать на истину и ложь. Получилось так: true/false, yes/no, on/off. Но и этого им показалось мало, стали возможны вариации написания с прописной буквы и всеми прописными, то есть: True/False, Yes/No, On/Off, а также: TRUE/FALSE, YES/NO, ON/OFF. И «контрольный выстрел»: y/n и Y/N также будут интерпретироваться как булев тип (подробнее).
Соответственно, если вы ожидаете, что в конфиге запись country: no будет обозначать Норвегию, а initial: Y — инициал человека по имени York, то вы глубоко заблуждаетесь. Чтобы избежать такой чехарды, надо заключить значение в кавычки. Сказанное в полной мере относится и к использованию этих значений в ключах.
В заключение отмечу, что в более поздних версиях стандарта языка YAML ряд спорных значений был убран, но надо понимать, что, во-первых, не все убраны, а, во-вторых, есть риск, что используемый вами парсер не поддерживает эти изменения.
Дата и время
date: 10/01/2023 — это не всегда 10 января. В разных системах это может быть интерпретировано по-разному. В США принято даты представлять в формате MM/DD/YYYY, поэтому гипотетически эта запись может оказаться первым октября.
Следствие: из-за возможной перестановки секций числа и месяца записи 31/12/2025 и 12/31/2025 вообще могут поломать логику в связи с отсутствием 31-го месяца в году.
Более надёжный способ работы с датами предполагает использование формата ISO 8601, чтобы следовать соглашению YYYY-MM-DD. В итоге, получим что-то вроде такого: date: 2023-01-10. Аналогичная рекомендация для случая, когда нужно сохранить время, получаем: date: 2023-01-10T12:05:00Z.
В качестве комментария: в OpenAPI для достижения аналогичного результата в случае строковых данных (type: string) используются соответственно format: date и format: date-time.
Комментарии
Одним из преимуществ YAML является поддержка комментариев, что делает их более понятными для восприятия. Однако понятность для человека может привести к непонятности для парсера. Рассмотрим 4 варианта.
name: Иванов Иван Иванович # Полное имя (ФИО)
name: Иванов Иван Иванович# Полное имя (ФИО)
name: "Иванов Иван Иванович" # Полное имя (ФИО)
name: "Иванов Иван Иванович"# Полное имя (ФИО)
Первый и третий будут восприняты правильно: значение ключа будет содержать только ФИО, а комментарий, следующий за символом решётки (#), будет проигнорирован вместе с самой решёткой. Однако второй и четвёртый варианты приведут к тому, что вся строка, включающая ФИО, символ решётки и текст комментария будет интерпретирована как одно большое значение. Что любопытно, так это то, что кавычки в четвёртом варианте тоже не помогут.
Чтобы избежать подобных недоразумений, следует всегда вставлять пробел между значением и последующим символом решётки.
Ещё больше полезного контента доступно в телеграм-канале.