YAML против JSON против TOML — Какой формат конфигурации вам действительно следует использовать?

Обновлено

YAML, JSON и TOML хранят настройки. Они не являются взаимозаменяемыми. YAML безусловно превращает ваш код страны в логическое значение. JSON не позволяет оставить комментарий. TOML — это тот, который никто из вашей команды ранее не использовал. Вот как выбрать правильный вариант.

YAML против JSON против TOML — какой формат конфигурации вы действительно должны использовать? 1
Реклама · УДАЛИТЬ?

В 2015 году плейбук Ansible сломался из-за этой строки:

country: NO

Конфигурация загружалась без ошибок. Нет жалоб на парсер. Но country не была установлена в строку "NO". Она была установлена в false. Поскольку в YAML 1.1, NO является логическим значением. Так же и yes, on, off, yи n. Это проблема Норвегии, и она уже годами тихо коррумпирует конфигурации.

Это не отчёт о баге YAML. Это рамка для решения, которое вы собираетесь принять: YAML, JSON или TOML для вашего следующего файла конфигурации? У каждого из них есть реальные компромиссы, и ответ «просто используйте то, что использует экосистема» не всегда применим.

Та же конфигурация, три способа

Перед разрушением, вот та же конфигурация приложения, написанная в трёх форматах:

ЯМЛ

# App configuration
app:
  name: my-api
  port: 8080
  debug: false

database:
  host: localhost
  port: 5432
  name: mydb
  pool_size: 10

logging:
  level: info
  format: json
  outputs:
    - stdout
    - /var/log/app.log

JSON

{
  "app": {
    "name": "my-api",
    "port": 8080,
    "debug": false
  },
  "database": {
    "host": "localhost",
    "port": 5432,
    "name": "mydb",
    "pool_size": 10
  },
  "logging": {
    "level": "info",
    "format": "json",
    "outputs": [
      "stdout",
      "/var/log/app.log"
    ]
  }
}

ТОМЛ

# App configuration
[app]
name = "my-api"
port = 8080
debug = false

[database]
host = "localhost"
port = 5432
name = "mydb"
pool_size = 10

[logging]
level = "info"
format = "json"
outputs = ["stdout", "/var/log/app.log"]

YAML — самый компактный, но самый синтаксически насыщенный. JSON — самый подробный, но самый прозрачный. TOML находится в середине: читаемый без имплицированных типовых преобразований YAML.

YAML: Сильный, гибкий и полный ловушек

YAML является стандартным выбором для CI/CD-процессов (GitHub Actions, GitLab CI, CircleCI), манифестов Kubernetes, плейбуков Ansible и большинства инструментов разработчиков. Вы не выбираете YAML — он выбирает вас.

Ловушки, документированные:

1. Проблема логических значений (проблема Норвегии)

YAML 1.1 — спецификация, которую большинство парсеров фактически реализуют — рассматривает большое количество строк как логические значения:

# YAML 1.1 boolean values (all parsed as true or false)
enabled: yes      # true
disabled: no      # false  
active: on        # true
paused: off       # false
valid: true       # true
invalid: false    # false

# The Norway Problem in practice:
country_codes:
  NO: Norway      # Key "NO" is fine, but value "NO" becomes false
  SE: Sweden
  YES: Yemen      # "YES" also becomes true

# The fix: quote your strings
country_codes:
  NO: "Norway"
  SE: "Sweden"

YAML 1.2 (выпущена в 2009 году) это исправило — только true и false являются логическими значениями. Проблема в том, что PyYAML не полностью перешёл к поведению 1.2 до версии 6.0 в 2021 году, а Go-парсер gopkg.in/yaml.v2 по-прежнему использует семантику 1.1 на 2024 год. Если вы используете Ruby Psych < 4.0 или любую версию PyYAML до 6.0, вы работаете с версией 1.1.

2. Табу убьёт вашу конфигурацию

YAML запрещает использование символов табуляции для отступов. Только пробелы являются допустимыми. Ваш редактор может отображать табуляцию и пробелы одинаково, файл может выглядеть корректно, но YAML всё равно выбросит:

yaml.scanner.ScannerError: while scanning a block mapping
  found character '\t' that cannot start any token

Это ошибка, которая заставляет молодых разработчиков сомневаться в своём выборе карьеры. Настройте ваш редактор так, чтобы табуляции расширялись в пробелы в файлах YAML. Каждый редактор поддерживает это; не каждый редактор включает это по умолчанию.

3. Многострочные строки не очевидны

# | (literal block): preserves newlines exactly
description: |
  Line one.
  Line two.
  Line three.
# Result: "Line one.\nLine two.\nLine three.\n"

# > (folded block): folds newlines into spaces
short_desc: >
  This will all become
  one long line.
# Result: "This will all become one long line.\n"

# Trailing newlines: | adds one, |+ adds all, |- strips them all
desc_stripped: |-
  No trailing newline.

Никто не запоминает это без поиска в справочнике. Мнемоника, которую я использую: | выглядит как перенос строки, > выглядит как что-то, сжатое вместе. Это всё ещё путано уже через три года.

Когда YAML выигрывает

  • Вы пишете манифесты Kubernetes, рабочие потоки GitHub Actions или плейбуки Ansible — у вас нет выбора.
  • Ваша конфигурация содержит много комментариев, объясняющих неочевидные значения. YAML поддерживает встроенные комментарии; JSON и TOML также поддерживают их, но YAML кажется наиболее естественным для конфигураций с большим объёмом аннотаций.
  • Ваша структура данных имеет глубоко вложенные структуры, которые выглядели бы ужасно при использовании плоских таблиц TOML.
  • Команда уже владеет этим и имеет линтер (yamllint) в процессе разработки.

JSON: Сухой, надёжный рабочий инструмент

JSON был разработан как формат обмена данными, а не как формат конфигурации. Дуглас Крочфорд намеренно исключил комментарии — его аргумент был в том, что комментарии будут использоваться для директив, на которые парсеры могут не согласоваться. Именно поэтому package.json не имеет комментариев и tsconfig.json является технически JSON с комментариями (JSONC), что является отдельным форматом, не поддерживаемым большинством JSON-парсеров.

Реальные проблемы JSON для конфигурационных файлов:

  • Отсутствие комментариев. Вы не можете объяснить, почему "maxRetries": 3 и не 5. Вы не можете оставить TODO. Вы не можете пометить поле как устаревшее. Это действительно болезненно для конфигурационных файлов, которые превышают сроки их авторов.
  • Отсутствие завершающих запятых. Добавление элемента в массив означает изменение предыдущей строки для добавления запятой. Каждое изменение JSON становится изменением в две строки. Каждый конфликт слияния становится немного хуже, чем нужно.
  • Несмотря на то, что для вложенных данных JSON является слишком подробным. Шесть строк скобок и квадратных скобок для того, что YAML делает за три строки отступов.
  • Все числа имеют одинаковый тип. JSON не различает целые и вещественные числа. 1 и 1.0 оба являются просто числами, и то, как ваша программа их десериализует, зависит от парсера.

Но предсказуемость JSON также является его главным преимуществом. У каждой языка есть JSON-парсер. Спецификация является однозначной. Нет имплицированных типовых преобразований. Строка всегда остаётся строкой — "yes" никогда не превращается в true. Если вам нужно программно проверить конфигурацию JSON, IO Tools’ JSON Formatter может обнаружить синтаксические ошибки до того, как они попадут в производство — полезно, когда кто-то ручным образом редактирует конфигурацию и забывает добавить завершающую запятую.

Когда JSON выигрывает

  • Конфигурации, которые потребляются несколькими сервисами или языками. JSON универсален; поддержка TOML в некоторых экосистемах нестабильна.
  • Вы нуждаетесь в строгих гарантиях типов. Валидация схем JSON зрелая, хорошо поддерживается и широко используется (VS Code использует её для автозаполнения настроек).
  • Конфигурация генерируется машиной. Никто не пишет JSON вручную, если это возможно — но машины генерируют его без проблем.
  • Вы работаете в Node.js или в фронтенд-JavaScript, где JSON является инструментом по умолчанию.

TOML: Конфигурация с уклоном, сделанная правильно

TOML (Tom’s Obvious, Minimal Language) была создана Томом Пренстон-Вернером, со-основателем GitHub, специально для конфигурационных файлов. Она достигла версии 1.0 в январе 2021 года. Это стандартный формат для Rust’а Cargo.toml, Python’а pyproject.tomlи статических сайтов Hugo.

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

# Types are unambiguous in TOML
name = "my-app"          # string: always quoted
port = 8080              # integer
threshold = 3.14         # float
enabled = true           # boolean: only true/false, no yes/no
created = 2024-01-15     # date: native type
tags = ["api", "prod"]   # array

# "yes" is just a string. Always.
country = "NO"           # string "NO", no boolean nonsense

Недостатки:

  • Синтаксис массивов таблиц действительно неудобен. [[products]] и [products.details] выглядят похоже, но ведут себя совершенно по-разному. Спецификация имеет смысл; визуальное различие не подчёркивает.
  • Глубокая вложенность становится громоздкой. То, что YAML делает за 5 отступов, TOML делает за 3 отдельных заголовка секции. Для конфигураций более чем на 3 уровня глубины, TOML начинает казаться неподходящим инструментом.
  • Доступность парсеров. Парсеры TOML существуют для всех основных языков, но они варьируются по степени соответствия спецификации. Тест-сборник TOML compliance test suite регулярно выявляет крайние случаи. Парсеры JSON проходят гораздо больше тестов по использованию.
  • Опыт команды. Если вы используете TOML вне экосистемы Rust или Python, ожидайте, что хотя бы один член команды откроет PR с вопросом «что это за формат?»

Когда TOML выигрывает

  • Проекты на Rust — Cargo.toml является стандартом и инструменты отличные.
  • Проекты на Python, использующие pyproject.toml (PEP 518) — это теперь рекомендуемое место для конфигурации инструментов, таких как Black, Ruff, mypy и pytest.
  • Простые, плоские конфигурации, где чувствительность к отступам YAML будет являться недостатком.
  • Вы хотите поддержку дат и времени без сериализации в строки.

Быстрый руководство по выбору

  • Kubernetes / CI/CD / Ansible? YAML. Нет выбора.
  • Конфигурация API, потребляемая несколькими сервисами в разных языках? JSON.
  • Проект на Rust? TOML (по умолчанию Cargo.toml).
  • Конфигурация проекта на Python (инструменты, форматы, сборка)? TOML (pyproject.toml — стандарт сейчас).
  • Конфигурация статического сайта (Hugo, Zola)? TOML, хотя эти обычно поддерживают все три.
  • Конфигурация проекта на Node.js? JSON (экосистема package.json), или YAML, если вам нужны комментарии.
  • Люди будут часто редактировать это и оставлять заметки? YAML или TOML (оба поддерживают комментарии). Не JSON.
  • Вы хотите строгую типовую безопасность и валидацию схемы? JSON + JSON Schema.

Честный ответ для большинства новых проектов: используйте то, что ожидает основной язык экосистемы. Rust ожидает TOML. Инструменты на Python ожидает TOML или YAML. Node.js ожидает JSON. Если вы пишете что-то универсальное, TOML для конфигурации, редактируемой людьми, и JSON для конфигурации, генерируемой машиной и потребляемой машиной — это разумный стандартный выбор.

Хотите убрать рекламу? Откажитесь от рекламы сегодня

Установите наши расширения

Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска

в Расширение Chrome в Расширение края в Расширение Firefox в Расширение Opera

Табло результатов прибыло!

Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!

Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?

новости с техническими моментами

Примите участие

Помогите нам продолжать предоставлять ценные бесплатные инструменты

Купи мне кофе
Реклама · УДАЛИТЬ?