Как могла бы выглядеть поддержка JSON в современном С++
Хорошо в плане поддержки JSON живётся программистам на Javascript — по какому-то невероятному стечению обстоятельств там JSON входит в спецификацию самого языка: есть JSON — есть объект. Удобно. Неплохо дело обстоит и в языках, где JSON не входит в сам язык, но поддерживается стандартной библиотекой (Python, Ruby): импортируешь модуль — и готово.Жизнь программистов на С++ никогда не была особо простой — поддержки JSON у нас нет ни на уровне языка, ни в стандартной библиотеке. И не будет, возможно, никогда. «Тоже мне проблему нашел!» — скажут мне опытные коллеги — «Её там и не должно быть, С++ поставляется без «батареек». Для решения этой задачи мы…» и вот здесь они разделятся на два лагеря:
1. «Мы используем большой фреймворк (boost, Qt, POCO, другой), который применяется во всех наших проектах и умеет 150 000 разных вещей, в том числе и JSON.»2. «Мы придерживаемся подхода в котором для каждой задачи применяется своя легковесная библиотека. В частности, для JSON мы уже 150 000 лет назад выбрали отличную библиотеку %JSON_LIB%, которая прекрасно работает.»
Да, всё так и есть. Вот только…
Чем плох подход с использованием фреймворков Во-первых, тянуть в проект огромный фреймворк ради одного JSON — как-то уныло. Ну ладно, допустим фреймворк у вас был и так. Но тогда придётся писать работу с JSON в терминах фреймворка, а это, как правило, тихий ужас. Посмотрите, например, на документацию по JSON в Qt — куча собственных типов вроде QJsonArray, QJsonDocument, QJsonObject, QJsonValue и т.д. и их придётся использовать. О том, чтобы потом перенести код в другой проект (где этого фреймворка нет) можно сразу забыть. Ну или вот Boost: парсер JSON находится очень логично в модуле Boost.PropertyTree. Ага, так бы я и догадался. Т.е. нам предлагают плясать не от формата JSON, а от структуры данных «дерево», которая умеет себя читать в том числе и из JSON.В общем, фреймворки навязывают нам своё виденье задачи, свой способ её решения и стремятся навсегда привязать нас к себе. Нет, если вы уверены, что нашли тот самый единственный и неповторимый фреймворк и будете с ним счастливы до конца жизни — воля ваша. Но я как-то не сторонник подобного фатализма.
Чем плох подход с использованием библиотек Плох он вот этой частью:»…150 000 лет назад выбрали отличную библиотеку…». Скорее всего речь идёт о чём-то, что начинало писаться чуть-ли не во времена DOSа и, без сомнения, работает, но при этом, пытаясь быть совместимым со всеми платформами и стандартами языка совершенно отстаёт от прогресса. Да, всё компилируется и работает, даже тесты проходит. Но библиотека совершенно не знакома с такими вещами, как ключевое слово auto, range-based циклы, строковые литералы, raw-строки, конструкторы перемещения, списки инициализации и прочие классные вещи, делающие код одновременно более эффективным и более легко читаемым. А ведь у библиотеки, созданной годы назад, есть обязательства по обратной совместимости, а значит просто так взять и добавить это всё она не может.
Давайте немного помечтаем.
А что, если бы JSON вошел в стандартную библиотеку нового стандарта С++? Что, если бы он был написан в терминах С++11\14 и без требований обратной совместимости со старыми стандартами языка? Что, если бы синтаксис этого модуля попытались бы сделать максимально приближенным к родному для JSON использованию «а-ля Javascript», но в том же время сохранить дух С++ (эффективность, минимальное потребление памяти, совместимость с STL)? Что, если бы его можно было включить в проект одним инклюдом и не беспокоиться о его сборке и линковке? Как бы это всё выглядело и работало?
И у нас есть ответ на этот вопрос! Давайте посмотрим на JSON-библиотеку для С++ написанную в соответствии со всеми этими принципами, ну и вообще написанной людьми для людей, а не чужими для хищников, как это обычно бывает.
Итак, пусть у нам нужно создать вот такой JSON-объект:
{ «name»: «Habrahabr», «nothing»: nullptr, «answer»: { «everything»: 42 }, «companies»: [«Infopulse», «TM»], «user»: { «name»: «tangro», «active»: true } } Давайте возьмём и создадим! Качаем github.com/nlohmann/json/blob/master/src/json.hpp, включаем его инклюдом в cpp-файл. #include «json.hpp» using json = nlohmann: json;
// создаём пустой JSON-объект
json j;
// добавляем строку, которая будет храниться как std: string
j[«name»] = «Habrahabr»;
// добавляем пустой вложенный объект
j[«nothing»] = nullptr;
// число внутри вложенного объекта
j[«answer»][«everything»] = 42;
// добавляем массив строк (будет храниться как std: vector
json j2 = { {«name», «Habrahabr»}, {«nothing», nullptr}, {«answer», { {«everything», 42} }}, {«companies», {«Infopulse», «TM»}}, {«user», { {«name», «tangro»}, {«active», true} }} } Так, а где же все эти модные фишки С++11? А вот они. // создание объекта из строкового литерала json j = »{ \«active\»: true, \«pi\»: 3.141 }»_json; // использование raw string json j = R»( { «active»: true, «pi»: 3.141 } )»_json; // использование auto auto j = json: parse (»{ \«active\»: true, \«pi\»: 3.141 }»); // использование range-based for for (auto element: j) { std: cout << element << '\n'; } Так, ладно, я приверженец «классического» стиля, покажите мне совместимость с STL // создание массива, синтаксис "а-ля std::vector" json j; j.push_back("foo"); j.push_back(1); j.push_back(true); // создание объекта, синтаксис "а-ля std::map" json o; o["foo"] = 23; o["bar"] = false; // поиск элемента if (o.find("foo") != o.end()) { // не найдено } // итерация по массиву for (json::iterator it = j.begin(); it != j.end(); ++it) { std::cout << *it << '\n'; } // перегруженный оператор [] и метод at(), поддержка std::string const std::string tmp = j[0]; j[1] = 42; bool foo = j.at(2); // знакомые методы работы с контейнерами j.size(); // 3 элемента j.empty(); // true или false j.type(); // json::value_t::array j.clear(); // очищаем // дамп в строку std::string s = j.dump(); Ну а ещё есть поддержка создания JSON из любых STL-контейнеров (std::array, std::vector, std::deque, std::forward_list, std::list, std::set, std::multiset, std::unordered_set, std::unordered_multiset). Для линейно-упорядоченных контейнеров порядом элементов будет сохранён, для ассоциативных, понятное дело — нет.
Так, почти убедил А ещё есть 100% покрытие библиотеки тестами и гарантия отстутствия утечек памяти от Valgrind.Вот и всё. В общем, я понимаю, что это «всего-лишь ещё одна библиотека для поддержки JSON» и по сути ничем она от миллиона других не отличается. Но как же всё-таки с ней удобно работать!