Third article in series: Modern C++
Modern C++ series:
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<double>(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 <iostream>
and #include <fstream>
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<double>
to JSON. Then we can expand the std
namespace with two methods to_json
and from_json
:
namespace std {
/// std::complex<double>
void to_json(json &j, const std::complex<double> &complex_number) {
j = json{{"re", complex_number.real()},
{"im", complex_number.imag()}};
}
void from_json(const json &j, std::complex<double> &complex_number) {
complex_number.real(j.at("re").get<double>());
complex_number.imag(j.at("im").get<double>());
}
}
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<person_t>();
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
In the next article 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.
Top comments (0)