Введение
Когда приложение принимает JSON-данные из внешнего источника — REST API, конфигурационный файл, сообщение от другого сервиса — нужно убедиться, что структура данных соответствует ожиданиям, прежде чем пытаться её обработать. Ручная проверка каждого поля через цепочки if-условий быстро становится громоздкой и подверженной ошибкам пропуска проверки. JSON Schema — стандартный формат описания структуры JSON-документа (обязательные поля, типы, диапазоны значений, форматы строк), позволяющий декларативно описать ожидаемую структуру и проверить документ одним вызовом. В статье рассмотрим использование JSON Schema в Qt-проекте через сторонюю библиотеку.
Концепция
Сам Qt не предоставляет встроенной поддержки JSON Schema — для валидации нужна сторонняя библиотека, например valijson или nlohmann/json в сочетании с json-schema-validator. Схема описывается как сам JSON-документ с ключевыми словами type, required, properties, minimum/maximum, pattern и другими, определяющими ограничения. Валидатор сравнивает входной документ со схемой и возвращает список нарушений, если документ не соответствует ожидаемой структуре — это превращает валидацию в декларативное описание контракта данных, а не в императивный код проверки.
Пример кода
// user_schema.json — схема, описывающая ожидаемую структуру пользователя
{
"type": "object",
"required": ["id", "username", "email"],
"properties": {
"id": { "type": "integer", "minimum": 1 },
"username": { "type": "string", "minLength": 3, "maxLength": 32 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 150 },
"roles": {
"type": "array",
"items": { "type": "string", "enum": ["admin", "user", "moderator"] }
}
},
"additionalProperties": false
}
#include <nlohmann/json.hpp>
#include <nlohmann/json-schema.hpp>
#include <QFile>
#include <QByteArray>
#include <QDebug>
#include <iostream>
using nlohmann::json;
using nlohmann::json_schema::json_validator;
bool validateUserJson(const QByteArray &jsonData, const QByteArray &schemaData)
{
try {
json schema = json::parse(schemaData.toStdString());
json document = json::parse(jsonData.toStdString());
json_validator validator;
validator.set_root_schema(schema);
validator.validate(document); // выбрасывает исключение при несоответствии
qDebug() << "JSON-документ соответствует схеме";
return true;
} catch (const std::exception &e) {
qWarning() << "Ошибка валидации:" << e.what();
return false;
}
}
void demonstrateValidation()
{
QFile schemaFile("user_schema.json");
schemaFile.open(QIODevice::ReadOnly);
const QByteArray schemaData = schemaFile.readAll();
const QByteArray validUser = R"({
"id": 1,
"username": "alice",
"email": "alice@example.com",
"age": 30,
"roles": ["admin", "user"]
})";
const QByteArray invalidUser = R"({
"id": 1,
"username": "al",
"email": "alice@example.com"
})"; // username короче minLength=3
validateUserJson(validUser, schemaData); // true
validateUserJson(invalidUser, schemaData); // false, с подробным сообщением об ошибке
}
Пояснения к коду
Схема user_schema.json декларативно описывает, что документ должен быть объектом с обязательными полями id, username, email, и опционально — age и roles с указанными ограничениями (диапазон чисел, минимальная длина строки, допустимые значения элементов массива). additionalProperties: false запрещает любые поля, не описанные в схеме — это полезно для строгой проверки, что клиент не присылает неожиданные или опечатанные имена полей.
Функция validateUserJson загружает и схему, и проверяемый документ через nlohmann::json::parse, после чего json_validator::validate() выбрасывает исключение std::exception с подробным описанием, какое именно правило схемы было нарушено — это даёт точную диагностику без необходимости писать собственную логику для каждого отдельного правила.
Подводные камни
- Производительность валидации против схемы для документов, поступающих в высокочастотном потоке. Полная валидация по сложной схеме с множеством вложенных правил добавляет заметные накладные расходы по сравнению с минимальной точечной проверкой нужных полей — для горячих путей обработки данных стоит измерить реальное влияние валидации на производительность и при необходимости валидировать выборочно (например, только данные, поступающие из недоверенных источников).
- Версионирование схемы при изменении контракта API. Если схема меняется (новое обязательное поле, изменение типа существующего), старые клиенты, отправляющие документы по предыдущей версии схемы, начнут проваливать валидацию — нужна явная стратегия совместимости (опциональные поля по умолчанию, поддержка нескольких версий схемы одновременно на переходный период).
- Несовпадение интерпретации формата
format(email, date-time, uri) между разными библиотеками валидации. Стандарт JSON Schema определяетformatкак необязательную для строгой проверки аннотацию — некоторые валидаторы проверяют её строго, другие лишь как подсказку без фактической проверки; нужно явно проверить поведение конкретной используемой библиотеки. - Сложные схемы с
oneOf/anyOf/allOfдают неинформативные сообщения об ошибках. При нарушении составного условия валидатор может вернуть длинный список причин (по одной на каждый вариантoneOf, не подошедший документу), что усложняет для конечного пользователя или другого сервиса понимание, что именно нужно исправить во входных данных.