Let me first tell you why cereal gives out a more detailed style than the one you may wish for. The kernel is written to work with arbitrary serialization archives and uses an average approach that satisfies all of them. Imagine that a key type is something more complex than a string or arithmetic type - how can we serialize it in the simple way "key" : "value" ?
Also note that the grain expects to be the prototype of any data that it reads.
Considering what you want, it is possible with cereals, but there are several obstacles:
The biggest hurdle to overcome is the fact that the desired input serializes some unknown number of name / value pairs inside the JSON object, rather than a JSON array. the grain was designed to use JSON arrays when working with containers that can contain a variable number of elements, as this made the most sense when using the basic quickjson analyzer that it uses.
Secondly, cereals do not currently expect that a name in a pair of name-values ββwill actually be loaded into memory - it simply uses them as an organizational tool.
So, hassle-free execution, here is a fully working solution (can be made more elegant) for your problem with minimal changes in cereals (this actually uses the change for cereals 1.1 , current version 1.0):
Add this function to the JSONInputArchive :
//! Retrieves the current node name /*! @return nullptr if no name exists */ const char * getNodeName() const { return itsIteratorStack.back().name(); }
Then you can write a serialization specialization for std::map (or unordered, depending on what you prefer) for a couple of lines. Be sure to put this in the cereal namespace so that it can be found by the compiler. This code should exist in your own files somewhere:
namespace cereal { //! Saving for std::map<std::string, std::string> template <class Archive, class C, class A> inline void save( Archive & ar, std::map<std::string, std::string, C, A> const & map ) { for( const auto & i : map ) ar( cereal::make_nvp( i.first, i.second ) ); } //! Loading for std::map<std::string, std::string> template <class Archive, class C, class A> inline void load( Archive & ar, std::map<std::string, std::string, C, A> & map ) { map.clear(); auto hint = map.begin(); while( true ) { const auto namePtr = ar.getNodeName(); if( !namePtr ) break; std::string key = namePtr; std::string value; ar( value ); hint = map.emplace_hint( hint, std::move( key ), std::move( value ) ); } } } // namespace cereal
This is not the most elegant solution, but it works well. I left everything in the general template, but what I wrote above will only work in JSON archives, given the changes made. Adding a similar getNodeName() to the XML archive will most likely allow it to work there too, but obviously this doesn't make sense for binary archives.
To make this clean, you want to place enable_if around this for the archives it works with. You will also need to modify JSON archives in cereals to work with variable-sized JSON objects. To get an idea of ββhow to do this, see how the kernel sets the state in the archive when it receives the SizeTag series. Basically you need to make the archive not open the array and open the object instead, and then create your own version of loadSize() , which will see how large the object is (this will be Member in quickjson).
To see this in action, run this code:
int main() { std::stringstream ss; { cereal::JSONOutputArchive ar(ss); std::map<std::string, std::string> filter = {{"type", "sensor"}, {"status", "critical"}}; ar( CEREAL_NVP(filter) ); } std::cout << ss.str() << std::endl; { cereal::JSONInputArchive ar(ss); cereal::JSONOutputArchive ar2(std::cout); std::map<std::string, std::string> filter; ar( CEREAL_NVP(filter) ); ar2( CEREAL_NVP(filter) ); } std::cout << std::endl; return 0; }
and you will receive:
{ "filter": { "status": "critical", "type": "sensor" } } { "filter": { "status": "critical", "type": "sensor" } }