RapidJSON для C++: полное руководство с примерами
RapidJSON — это высокопроизводительная библиотека для парсинга и генерации JSON на языке C++. Разработанная Мило Ип (Milo Yip) и распространяемая под лицензией MIT, она завоевала популярность благодаря своей скорости, минимальному потреблению памяти и полному соответствию стандарту ECMA-404 (RFC 7159). В отличие от многих других библиотек, RapidJSON не требует внешних зависимостей и может работать в окружениях с ограниченными ресурсами, включая встраиваемые системы.
Зачем нужна эта библиотека? JSON (JavaScript Object Notation) стал де-факто стандартом для обмена данными между веб-сервисами, конфигурационными файлами и хранением структурированных данных. В C++, где производительность критична, использование медленного парсера может стать узким местом. RapidJSON решает эту проблему: он способен обрабатывать мегабайты JSON за миллисекунды, используя SAX-подобный (потоковый) API для минимизации аллокаций и DOM-API для удобной работы с деревом объектов. Это делает его идеальным выбором для высоконагруженных серверов, игровых движков и приложений реального времени.
Библиотека поддерживает как полный разбор JSON (с проверкой корректности), так и "нечувствительный" режим для ускорения. Она умеет работать с UTF-8, UTF-16 и UTF-32, а также предоставляет удобные итераторы и функции для поиска. RapidJSON активно используется в таких проектах, как MongoDB, Tencent и многих open-source решениях.
Установка
RapidJSON — это header-only библиотека (за исключением опциональных файлов для сборки). Это значит, что для её использования достаточно скопировать заголовочные файлы в ваш проект и подключить их. Однако для удобства мы рассмотрим три основных способа установки.
Способ 1: Клонирование репозитория (рекомендуется)
Склонируйте репозиторий с GitHub и скопируйте папку include/rapidjson в ваш проект.
git clone https://github.com/Tencent/rapidjson.git
# Затем скопируйте папку include/rapidjson в ваш проект
Способ 2: Через пакетный менеджер vcpkg
Если вы используете vcpkg, установка выполняется одной командой:
vcpkg install rapidjson
Способ 3: Через Conan
Для пользователей Conan (C/C++ package manager):
conan install rapidjson/1.1.0@
Подключение в коде
После установки добавьте в ваш исходный файл:
#include "rapidjson/document.h" // для DOM API
#include "rapidjson/writer.h" // для записи JSON
#include "rapidjson/stringbuffer.h" // для буферизации вывода
#include "rapidjson/prettywriter.h" // для форматированного вывода
#include "rapidjson/reader.h" // для SAX API
#include "rapidjson/error/en.h" // для обработки ошибок
Быстрый старт: минимальный рабочий пример
Давайте напишем простейшую программу, которая парсит JSON-строку, извлекает значение и выводит его на экран.
#include <iostream>
#include "rapidjson/document.h"
int main() {
// 1. JSON-строка для парсинга
const char* json = "{\"project\":\"RapidJSON\",\"version\":1.0}";
// 2. Создаём DOM-документ
rapidjson::Document doc;
doc.Parse(json);
// 3. Проверяем, успешен ли парсинг
if (doc.HasParseError()) {
std::cerr << "Ошибка парсинга: "
<< rapidjson::GetParseError_En(doc.GetParseError())
<< std::endl;
return 1;
}
// 4. Извлекаем значения по ключам
const std::string& project = doc["project"].GetString();
double version = doc["version"].GetDouble();
// 5. Выводим результат
std::cout << "Проект: " << project << std::endl;
std::cout << "Версия: " << version << std::endl;
return 0;
}
Этот пример показывает базовый цикл работы с RapidJSON: создание документа, парсинг, проверка ошибок и доступ к данным. Обратите внимание, что doc["key"] возвращает ссылку на Value, а методы GetString(), GetDouble() и т.д. извлекают типизированные данные.
Основные методы, функции и классы
RapidJSON предоставляет два основных API: DOM (Document Object Model) для удобной работы с деревом и SAX (Simple API for XML) для потоковой обработки. Ниже мы рассмотрим 8 ключевых компонентов.
1. Класс rapidjson::Document
Сигнатура: class Document : public Value { ... };
Что делает: Представляет корневой узел JSON-документа. Наследует все методы класса Value и добавляет методы для парсинга и управления памятью.
Пример:
rapidjson::Document doc;
doc.Parse(json_string);
if (doc.IsObject()) {
// работаем с объектом
}
2. Метод Document::Parse()
Сигнатура: ParseResult Parse(const Ch* str);
Что делает: Парсит JSON-строку и строит DOM-дерево. Возвращает ParseResult, который можно проверить на ошибки.
Пример:
rapidjson::Document doc;
doc.Parse("[1, 2, 3]");
if (doc.HasParseError()) {
std::cout << "Ошибка на позиции " << doc.GetErrorOffset() << std::endl;
} else {
std::cout << "Массив размером: " << doc.Size() << std::endl;
}
3. Метод Value::GetString()
Сигнатура: const Ch* GetString() const;
Что делает: Возвращает строковое значение узла. Узел должен быть строкой (IsString() == true).
Пример:
rapidjson::Document doc;
doc.Parse("{\"name\": \"Alice\"}");
const char* name = doc["name"].GetString();
std::cout << "Name: " << name << std::endl; // Вывод: Name: Alice
4. Метод Value::GetInt() / GetDouble() / GetBool()
Сигнатура: int GetInt() const; / double GetDouble() const; / bool GetBool() const;
Что делает: Извлекают числовое или булево значение. Для чисел RapidJSON автоматически определяет тип (int, unsigned int, int64_t, double).
Пример:
rapidjson::Document doc;
doc.Parse("{\"age\": 30, \"pi\": 3.14, \"active\": true}");
int age = doc["age"].GetInt();
double pi = doc["pi"].GetDouble();
bool active = doc["active"].GetBool();
std::cout << age << " " << pi << " " << active << std::endl;
5. Метод Value::HasMember()
Сигнатура: bool HasMember(const Ch* key) const;
Что делает: Проверяет, существует ли ключ в объекте. Позволяет избежать ошибок доступа к несуществующим полям.
Пример:
rapidjson::Document doc;
doc.Parse("{\"name\": \"Bob\"}");
if (doc.HasMember("age")) {
std::cout << "Age: " << doc["age"].GetInt();
} else {
std::cout << "Поле age отсутствует";
}
6. Класс rapidjson::Writer
Сигнатура: template<typename OutputStream, typename SourceEncoding, typename TargetEncoding, unsigned StackLevel> class Writer;
Что делает: Позволяет генерировать JSON-строку в потоковом режиме (SAX-стиль). Эффективнее, чем создание DOM и его сериализация.
Пример:
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
writer.StartObject();
writer.Key("name");
writer.String("Charlie");
writer.Key("age");
writer.Int(25);
writer.EndObject();
std::cout << buffer.GetString() << std::endl; // {"name":"Charlie","age":25}
7. Класс rapidjson::PrettyWriter
Сигнатура: class PrettyWriter : public Writer { ... };
Что делает: Наследует Writer, но добавляет форматирование (отступы, переносы строк) для читаемого вывода.
Пример:
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
writer.StartObject();
writer.Key("data");
writer.StartArray();
writer.Int(1);
writer.Int(2);
writer.EndArray();
writer.EndObject();
std::cout << buffer.GetString() << std::endl;
/* Вывод:
{
"data": [
1,
2
]
}
*/
8. Класс rapidjson::Reader и rapidjson::BaseReaderHandler
Сигнатура: class Reader { ... }; template<typename Encoding, typename Derived> class BaseReaderHandler;
Что делает: Реализует SAX-подобный парсинг. Вы определяете обработчик (handler), который получает события (начало объекта, ключ, значение и т.д.). Это позволяет обрабатывать JSON без построения полного DOM-дерева, экономя память.
Пример (простой обработчик):
struct MyHandler : public rapidjson::BaseReaderHandler<rapidjson::UTF8<>, MyHandler> {
bool Null() { std::cout << "null" << std::endl; return true; }
bool Bool(bool b) { std::cout << (b ? "true" : "false") << std::endl; return true; }
bool Int(int i) { std::cout << i << std::endl; return true; }
bool String(const char* str, rapidjson::SizeType length, bool copy) {
std::cout << "string: " << str << std::endl; return true;
}
bool StartObject() { std::cout << "{" << std::endl; return true; }
bool Key(const char* str, rapidjson::SizeType length, bool copy) {
std::cout << "key: " << str << std::endl; return true;
}
bool EndObject(rapidjson::SizeType memberCount) { std::cout << "}" << std::endl; return true; }
bool StartArray() { std::cout << "[" << std::endl; return true; }
bool EndArray(rapidjson::SizeType elementCount) { std::cout << "]" << std::endl; return true; }
};
rapidjson::Reader reader;
MyHandler handler;
const char* json = "{\"a\": 1, \"b\": [2, 3]}";
rapidjson::StringStream ss(json);
reader.Parse(ss, handler);
Реальный практический пример: парсинг конфигурационного файла и генерация отчёта
Представьте, что у нас есть JSON-файл config.json с настройками приложения и списком пользователей. Мы хотим прочитать его, проверить корректность, а затем сгенерировать отчёт в формате JSON.
#include <iostream>
#include <fstream>
#include <string>
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/error/en.h"
// Функция для чтения файла в строку
std::string readFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
throw std::runtime_error("Не удалось открыть файл: " + filename);
}
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
return content;
}
int main() {
try {
// 1. Читаем JSON-файл
std::string jsonContent = readFile("config.json");
// 2. Парсим JSON
rapidjson::Document doc;
doc.Parse(jsonContent.c_str());
if (doc.HasParseError()) {
std::cerr << "Ошибка парсинга JSON: "
<< rapidjson::GetParseError_En(doc.GetParseError())
<< " (позиция: " << doc.GetErrorOffset() << ")"
<< std::endl;
return 1;
}
// 3. Проверяем структуру документа
if (!doc.Is