json_dto is a thin wrapper around RapidJSON library we wrote several years ago. It's surprising for us that this small bicycle is used by someone outside our team. Sometimes users show us use-cases we just weren't thinking about. And we add new features to json_dto to fulfill expectations.
The last updates to json_dto add a possibility to customize formatting of (de)serialized values by specifying custom Reader_Writers. For example let's imagine that we have a struct with a `std::map<std::uint32_t, int>` field:
struct example_data { std::map<std::uint32_t, int> m_weights; ... }; |
It's impossible to write a simple (de)serialization code for example_data in the form:
struct example_data { std::map<std::uint32_t, int> m_weights; ... template<typename Io> void json_io(Io & io) { io & json_dto::mandatory("weights", m_weights) ... ; } }; |
because RapidJSON expects string values as keys, but json_dto (de)serializes `std::uint32_t` as integers. And the naive implementation of `json_io` shown above leads to run-time error during serialization.
Now json_dto allows to use custom formatters for fields to be (de)serialized:
struct simple_reader_writer { // Those methods will be used for (de)serializing of map's keys. void read( json_dto::mutable_map_key_t<std::uint32_t> & key, const rapidjson::Value & from) { ... // Deserializing key from a string. } void write( const json_dto::mutable_map_key_t<std::uint32_t> & key, rapidjson::Value & to, rapidjson::MemoryPoolAllocator<> & allocator) { ... // Serializing key as a string. } // Those methods will be used for (de)serializing of map's values. void read( int & v, const rapidjson::Value & from) { json_dto::read_json_value(v, from); // Just reuse json_dto. } void write( const int & v, rapidjson::Value & to, rapidjson::MemoryPoolAllocator<> & allocator) { json_dto::write_json_value(v, to, allocator); // Just reuse json_dto. } }; struct example_data { std::map<std::uint32_t, int> m_weights; ... template<typename Io> void json_io(Io & io) { io & json_dto::mandatory( // Explicit specification of custom Reader_Writer for that field. // The usage of apply_to_content_t tells that custom formatter // should be applied to every item of the container. json_dto::apply_to_content_t<simple_reader_writer>{}, "weights", m_weights) ... ; } }; |
So now keys of `example_data::m_weights` will be (de)serialized as strings.
But custom Reader_Writers allow to go further. Let's imaging that `example_data` contains yet another `std::map<uint32_t, int>`:
struct example_data { std::map<std::uint32_t, int> m_weights; std::map<std::uint32_t, int> m_colors; ... }; |
where keys of `example_data::m_colors` should be represented in the form `#xxxxxx`, where `xxxxxx` is hexadecimal representation.
We can create another custom Reader_Writer:
struct color_hex_reader_writer { // Those methods will be used for (de)serializing of map's keys. void read( json_dto::mutable_map_key_t<std::uint32_t> & key, const rapidjson::Value & from) { ... // Deserializing key from a string. } void write( const json_dto::mutable_map_key_t<std::uint32_t> & key, rapidjson::Value & to, rapidjson::MemoryPoolAllocator<> & allocator) { ... // Serializing key as a string. } // Those methods will be used for (de)serializing of map's values. void read( int & v, const rapidjson::Value & from) { json_dto::read_json_value(v, from); // Just reuse json_dto. } void write( const int & v, rapidjson::Value & to, rapidjson::MemoryPoolAllocator<> & allocator) { json_dto::write_json_value(v, to, allocator); // Just reuse json_dto. } }; |
and use it for (de)serialization of `example_data::m_colors`:
struct example_data { std::map<std::uint32_t, int> m_weights; std::map<std::uint32_t, int> m_colors; ... template<typename Io> void json_io(Io & io) { io & json_dto::mandatory( json_dto::apply_to_content_t<simple_reader_writer>{}, "weights", m_weights) & json_dto::mandatory( json_dto::apply_to_content_t<color_hex_reader_writer>{}, "colors", m_colors) ... ; } }; |
The full working example can be seen here.
PS. I don't like to answer questions like "Is json_dto better than nlohmann::json, cereal or any other similar library?" We started to use RapidJSON several years ago and we didn't know about many of the JSON-libraries well known today. At some time we decided to simplify our marriage with RapidJSON and wrote json_dto. Since then it's easier for us to continue to use json_dto than to switch to any other library. So if you are happy with nlohmann::json there is no need to see for something else. But if you have to stay out of nlohmann::json for any reason there is json_dto ;)
PPS. We love reinventing bikes and we know how to do it: RESTinio and SObjectizer are also out of our garage.