What JSON support in modern C ++ might look like

  • Tutorial
Javascript programmers live well in terms of JSON support - for some incredible combination of circumstances, there JSON is included in the specification of the language itself: there is JSON - there is an object. Conveniently. A good thing is in languages ​​where JSON is not included in the language itself, but is supported by the standard library (Python, Ruby): you import the module and you're done.

The life of C ++ programmers has never been particularly simple - we do not have JSON support either at the language level or in the standard library. And probably never will be. “I also found a problem!” - experienced colleagues will tell me - “It should not be there, C ++ is delivered without“ batteries ”. To solve this problem, we ... "and here they are divided into two camps:

1.“We use a large framework (boost, Qt, POCO, another), which is used in all our projects and can do 150,000 different things, including JSON.”
2. “We follow an approach in which each task uses its own lightweight library. In particular, for JSON, we already 150,000 years ago chose the excellent library% JSON_LIB%, which works great. "

Yes, that's right. That's just ...

What is a bad approach using frameworks
Firstly, pulling a huge framework into a project for the sake of one JSON is somehow depressing. Well, let's say you had a framework like that. But then you have to write work with JSON in terms of the framework, and this is usually quiet horror. Look, for example, at the JSON documentation in Qt - a bunch of native types like QJsonArray, QJsonDocument, QJsonObject, QJsonValue, etc. and you have to use them. You can immediately forget about transferring the code to another project (where this framework is not). Well, or Boost: the JSON parser is very logical in the Boost.PropertyTree module . Yeah, I would have guessed. Those. we are offered to dance not from the JSON format, but from the "tree" data structure, which can read itself, including from JSON.

In general, frameworks impose on us their vision of a problem, their own way of solving it, and strive to permanently tie us to themselves. Not if you are sure that you have found that very one and only framework and will be happy with it until the end of your life - your will. But somehow I am not a supporter of such fatalism.

Why the library approach is bad
He’s bad with this part: "... 150,000 years ago they chose an excellent library ...". Most likely, we are talking about something that began to be written almost at the time of DOS and, without a doubt, works, but at the same time, trying to be compatible with all platforms and language standards, it completely lags behind progress. Yes, everything compiles and works, even tests pass. But the library is completely unfamiliar with things like the auto keyword, range-based loops, string literals, raw strings, move constructors, initialization lists, and other cool things that make the code both more efficient and easier to read. But the library, created years ago, has obligations for backward compatibility, which means that it just cannot take and add all this.

Let's dream a little.

But what if JSON went into the standard library of the new C ++ standard? What if it were written in C ++ 11 \ 14 terms and without backward compatibility requirements with old language standards? What if the syntax of this module would be tried to make it as close as possible to the native JSON usage “a la Javascript”, but at the same time to preserve the spirit of C ++ (efficiency, minimal memory consumption, compatibility with STL)? What if it could be included in the project with one inclusion and not worry about its assembly and linking? How would it all look and work?

And we have the answer to this question! Let's take a look at the JSON libraryfor C ++ written in accordance with all these principles, well, generally written by people for people, and not strangers to predators, as is usually the case.

So, let us need to create such a JSON object:

  "name": "Habrahabr",
  "nothing": null,
  "answer": {
    "everything": 42
  "companies": ["Infopulse", "TM"],
  "user": {
    "name": "tangro",
    "active": true

Let's take and create!

Download github.com/nlohmann/json/blob/master/src/json.hpp , include it with the inclusion in the cpp-file.

#include "json.hpp"
using json = nlohmann::json;

// создаём пустой JSON-объект
json j;

// добавляем строку, которая будет храниться как std::string
j["name"] = "Habrahabr";

// добавляем пустой вложенный объект
j["nothing"] = nullptr;

// число внутри вложенного объекта
j["answer"]["everything"] = 42;

// добавляем массив строк (будет храниться как std::vector)
// обратите внимание - используются списки инициализации
j["companies"] = { "Infopulse", "TM" };

// добавляем ещё один объект - на этот раз используем список инициализации с парами "ключ"-"значение"
j["user"] = { {"name", "tangro"}, {"active", true} };

By the way, using the last feature, instead of all this, you could write object creation very similar to the JSON format itself:

json j2 = 
	  {"name", "Habrahabr"},
	  {"nothing", nullptr},
	  {"answer", {
	    {"everything", 42}
	  {"companies", {"Infopulse", "TM"}},
	  {"user", {
	    {"name", "tangro"},
	    {"active", true}

So, where are all these fancy C ++ 11 features?

And here they are.

// создание объекта из строкового литерала
json j = "{ \"active\": true, \"pi\": 3.141 }"_json;

// использование raw string
json j = R"(
    "active": true,
    "pi": 3.141

// использование auto
auto j = json::parse("{ \"active\": true, \"pi\": 3.141 }");

// использование range-based for
for (auto element : j) {
  std::cout << element << '\n';

So, okay, I'm a fan of the "classic" style, show me compatibility with STL

// создание массива, синтаксис "а-ля std::vector"
json j;

// создание объекта, синтаксис "а-ля 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();

Well, there is also support for creating JSON from any STL containers (std :: array, std :: vector, std :: deque, std :: forward_list, std :: list, std :: set, std :: multiset, std: : unordered_set, std :: unordered_multiset). For linearly ordered and ordered associative containers, the order of the elements will be preserved, for unordered-associative containers, of course, no.

So, almost convinced

There is also 100% test coverage of the library and a guarantee of no memory leaks from Valgrind.

That's all.

In general, I understand that this is “just another library for supporting JSON” and in fact it is no different from a million others. But how is it nevertheless convenient to work with her!

Also popular now: