Работа с файлами и директориями в Qt

Чтение конфигов в формате INI/TOML/YAML в Qt-проекте: сравнение подходов

Введение

Конфигурационные файлы — неотъемлемая часть большинства приложений. Qt из коробки прекрасно поддерживает формат INI через QSettings, но всё больше проектов выбирают TOML или YAML за читаемость и выразительность. Разберём, какие средства доступны в экосистеме Qt для каждого формата и когда какой выбрать.

Концепция

  • INI — нативно поддерживается через QSettings с форматом QSettings::IniFormat. Простой, давно стандартизированный, но ограниченный формат: плоская иерархия секций, нет встроенной поддержки списков сложных структур и типов данных без дополнительных соглашений.
  • JSON — поддерживается нативно через QJsonDocument, QJsonObject, QJsonArray (модуль QtCore). Хорошо подходит, если конфигурация структурирована иерархически и не требует комментариев (JSON их не поддерживает).
  • YAML — нет встроенной поддержки в Qt. Требуются сторонние библиотеки, например yaml-cpp (на C++) с собственноручной прослойкой конвертации в QVariant/QJsonValue.
  • TOML — также нет встроенной поддержки; используются библиотеки toml++ (header-only, современный C++17) или cpptoml.

Пример кода: Чтение конфигов в

Сравним три подхода на одной и той же задаче — чтении конфигурации сервера (хост, порт, список разрешённых пользователей).

1. QSettings (INI)

#include <QSettings>
#include <QDebug>

void loadIniConfig()
{
    QSettings settings("server.ini", QSettings::IniFormat);

    settings.beginGroup("network");
    QString host = settings.value("host", "127.0.0.1").toString();
    int port = settings.value("port", 8080).toInt();
    settings.endGroup();

    QStringList users = settings.value("auth/allowed_users").toStringList();

    qDebug() << "INI:" << host << port << users;
}

Содержимое server.ini:

[network]
host=192.168.1.10
port=9090

[auth]
allowed_users=admin, operator, guest

2. QJsonDocument (JSON)

#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>

void loadJsonConfig()
{
    QFile file("server.json");
    if (!file.open(QIODevice::ReadOnly))
        return;

    QJsonParseError error;
    QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
    if (error.error != QJsonParseError::NoError) {
        qWarning() << "Ошибка парсинга JSON:" << error.errorString();
        return;
    }

    QJsonObject root = doc.object();
    QJsonObject network = root["network"].toObject();

    QString host = network.value("host").toString("127.0.0.1");
    int port = network.value("port").toInt(8080);

    QJsonArray usersArray = root["auth"].toObject()["allowed_users"].toArray();
    QStringList users;
    for (const QJsonValue &v : usersArray)
        users << v.toString();

    qDebug() << "JSON:" << host << port << users;
}

3. toml++ (TOML), требует внешней библиотеки

#include <toml++/toml.h>
#include <QDebug>
#include <QString>

void loadTomlConfig()
{
    toml::table config;
    try {
        config = toml::parse_file("server.toml");
    } catch (const toml::parse_error &err) {
        qWarning() << "Ошибка парсинга TOML:" << err.description().data();
        return;
    }

    QString host = QString::fromStdString(
        config["network"]["host"].value_or(std::string("127.0.0.1")));
    int port = config["network"]["port"].value_or(8080);

    QStringList users;
    if (auto arr = config["auth"]["allowed_users"].as_array()) {
        for (auto &&el : *arr) {
            if (auto s = el.value<std::string>())
                users << QString::fromStdString(*s);
        }
    }

    qDebug() << "TOML:" << host << port << users;
}

Содержимое server.toml:

[network]
host = "192.168.1.10"
port = 9090

[auth]
allowed_users = ["admin", "operator", "guest"]

Пояснения к коду

QSettings автоматически кеширует значения и умеет писать конфиги обратно тем же API (setValue()), что делает её удобной для пользовательских настроек, изменяемых самим приложением. JSON и TOML обычно используются как «читай-только» конфигурация, поставляемая вместе с приложением или редактируемая администратором вручную, поэтому запись назад реализуется отдельно через сериализацию объекта целиком.

Подводные камни

  1. QSettings на Windows может писать в реестр. Если явно не указать QSettings::IniFormat, на Windows по умолчанию используется системный реестр (QSettings::NativeFormat), что неожиданно для разработчиков, привыкших к Linux/macOS, где NativeFormat соответствует INI-подобным файлам.
  1. Типы в INI всегда текстовые. QSettings хранит и читает значения как QVariant, но физически в INI-файле всё представлено текстом — при ручном редактировании файла легко внести типовую ошибку (например, записать список без запятых), которая будет молча неверно интерпретирована.
  1. JSON не поддерживает комментарии. Если конфигурацию предполагается редактировать людьми (а не только программой), отсутствие комментариев в JSON — серьёзное неудобство по сравнению с TOML или YAML.
  1. Подключение сторонних библиотек усложняет сборку. toml++ header-only и легко подключается, а yaml-cpp требует сборки и линковки — это увеличивает сложность CI и сборочных скриптов, особенно при кросс-компиляции для мобильных платформ.
  1. Отсутствие единообразной обработки ошибок. QJsonDocument::fromJson() возвращает структуру ошибки через выходной параметр, а toml++ бросает исключения — при смешивании форматов в одном проекте код обработки ошибок должен быть продуман для каждого случая отдельно.
  1. YAML особенно подвержен скрытым ошибкам типизации. Известная проблема YAML — автоматическое приведение строк типа no, yes, on, off к булевым значениям, что может неожиданно сломать конфигурацию (например, страна с кодом «NO» превратится в false). При использовании YAML стоит явно квотировать строковые значения, которые могут совпасть со специальными литералами.

Выбор формата конфигурации в Qt-проекте — это компромисс между встроенной поддержкой (INI, JSON) и удобством для человека (TOML, YAML), требующим внешних зависимостей. Для большинства внутренних настроек приложения достаточно QSettings, а для сложных конфигураций, читаемых и редактируемых разработчиками или DevOps-инженерами, разумно подключить toml++.

================================================================================