Как могла бы выглядеть поддержка 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<std::string>) // обратите внимание - используются списки инициализации j["companies"] = { "Infopulse", "TM" }; 
// добавляем ещё один объект - на этот раз используем список инилиализации с парами "ключ"-"значение" j["user"] = { {"name", "tangro"}, {"active", true} }; 

Кстати, используя последнюю фичу, вместо всего этого можно было бы написать создания объекта очень похожее на сам JSON-формат:

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» и по сути ничем она от миллиона других не отличается. Но как же всё-таки с ней удобно работать!

ссылка на оригинал статьи http://habrahabr.ru/post/254075/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *