♦ πŸ† 3 min, 🐌 6 min

JSON in C++

Third article in series: Modern C++

One thing in python that's super useful are dictionaries and JSON. They allow flexible data structures. What about JSON structures? They are not possible in a statically typed language like C++. We'll modern C++ can handle JSON believe it or not πŸ™‚C++ itself doesn't provide JSON, but there are a few options that bring JSON type data structure to C++.

I personally use C++ JSON library nlohman . There are other libraries, though, so take your pick. I like nlohman JSON lib because it's a single header file library (drop header file into the project). Or it can also be used as a dependency. It depends if you want to ship the JSON lib with your software or allow the user to add it as a dependency.

What can the JSON library do? Well, quite a lot, but the syntax and usage can be quite confusing, so I'll provide a few examples to clear things out.

Note: I'm assuming ' using nlohmann::json; to write less syntax.

To create a JSON object, we simply do:

json data;

and then add elements to it:

// vector
data["M_final"] = std::vector(N);

// a double
data["beta"] = 10.0;

or any other structure from STL library. Some STL structures are not supported though most are. More on adding custom structures to JSON later.

There are a lot more options on how to construct the json object. For example, we can actually write the JSON format in the program, but I prefer to load JSON data structures from an external file. json object creation inside the codebase:

json data = R"(
{
"happy": true,
"pi": 3.141
}
)"_json;

For more construction options, look into the docs. The library has a ton of other functionality STL like access, for example, but really read the docs if you are interested in more.

Read & write JSON files

How do we load JSON file into the JSON data object from C++? We'll quite easyly. If we specify:

std::string path = "path to json file";
std::string file_name = "json_file_name";
we then load the file with few lines of code:

json data;
std::ifstream file(path + "/" + file_name + ".json");
file >> data;

Side note. You need to include #include and #include libraries. But they are in STL so no need for messy installs.

Then to store to the JSON file:

std::ofstream file(path + "/" + file_name + ".json");
file << std::setw(4) << data << std::endl;

Custom JSON serializers

So let's say we want to store a custom data structure to a JSON file. For that, we need to define a serializer. JSON serializer tells our library how it should convert a C++ object into a format that can be written into a JSON file. Or in other words, it tells the software how to load the JSON file into appropriate C++ data structure.

For example, let's say we want to be able to store std::complex to JSON. Then we can expand the std namespace with two methods to_json and from_json:

namespace std {

/// std::complex
void to_json(json &j, const std::complex &complex_number) {
j = json{{"re", complex_number.real()},
{"im", complex_number.imag()}};
}

void from_json(const json &j, std::complex &complex_number) {
complex_number.real(j.at("re").get());
complex_number.imag(j.at("im").get());
}
}

Or to add a structure of type person:

struct person_t {
std::string name;
std::string address;
int age;
};

void to_json(json &j, const person_t &p) {
j = json{{"name", p.name},
{"address", p.address},
{"age", p.age}};
}

void from_json(const json &j, person_t &p) {
j.at("name").get_to(p.name);
j.at("address").get_to(p.address);
j.at("age").get_to(p.age);
}

By providing to_json and from_json we allow the program to automatically map our data structure to the JSON format. Now we can store a person structure to the JSON file via:

person_t p;
// define person: name, address, age
json data;
data["person"] = p;

If we stored json data now into a JSON file, the content would be:

"person": {
"name": "John",
"address": "web",
"age": "31"
}

That's it. OK when reading back from JSON file we actually need to provide the data structure type before we can get the C++ function. For example to retrieve our person object:

json data; // contains person object but in string format
auto p = data.at("person").get();

I know that's not as pretty as in python, but if you store the type of the data structures when you create the JSON file, you can use a little trick. You can write an extra wrapper around json library that allows you to make calls of type:

enhanced_json data;
person_p p = data.at("person");

In the above case, you handle data loading based on the data type in the JSON file inside the enhanced_json object using the at function. To get the enhanced_json object, you need a few if statements and auto.

Sure all the above software will add overhead and make your code slower. So if you need fast data storage for large amounts of data, this automatic serialization is probably not a good idea. I would argue that JSON is not a good idea if you need to read and store a lot of data. But if you need C++ to do a lot of data crunching and need a bit of storage. Then JSON interface can tremendously speed up your workflow.

JSON doesn't make C++ totallyΒ python

Next week I'll dive into the use of C++ JSON data structures to speed up the analysis workflow. Until then, if you know any more tricks that make C++ more pythonic, let me know.

Get notified & read regularly πŸ‘‡