Is there a way to specify a simpler JSON (de-)serialization for std::map using Cereal / C++?

℡╲_俬逩灬. 提交于 2019-12-03 12:40:35

Let me first address why cereal outputs a more verbose style than one you may desire. cereal is written to work with arbitrary serialization archives and takes a middle ground approach of satisfying all of them. Imagine that the key type is something entirely more complicated than a string or arithmetic type - how could we serialize it in a simple "key" : "value" way?

Also note that cereal expects to be the progenitor of any data it reads in.


That being said, what you want is entirely possible with cereal but there are a few obstacles:

The largest obstacle to overcome is the fact that your desired input serializes some unknown number of name-value pairs inside of a JSON object and not a JSON array. cereal was designed to use JSON arrays when dealing with containers that can hold a variable number of elements, since this made the most sense given the underlying rapidjson parser it uses.

Secondly, cereal currently does not expect the name in a name-value-pair to actually be loaded into memory - it just uses them as an organizational tool.


So rambling done, here is a fully working solution (could be made more elegant) to your problem with very minimal changes to cereal (this in fact uses a change that is slated for cereal 1.1, the current version is 1.0):

Add this function to JSONInputArchive:

//! Retrieves the current node name
/*! @return nullptr if no name exists */
const char * getNodeName() const
{
  return itsIteratorStack.back().name();
}

You can then write a specialization of the serialization for std::map (or unordered, whichever you prefer) for a pair of strings. Make 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 isn't the most elegant solution, but it does work well. I left everything generically templated but what I wrote above will only work on JSON archives given the changes made. Adding a similar getNodeName() to the XML archive would likely let it work there too, but obviously this wouldn't make sense for binary archives.

To make this clean, you'd want to put enable_if around that for the archives it works with. You would also need to modify the JSON archives in cereal to work with variable sized JSON objects. To get an idea of how to do this, look at how cereal sets up state in the archive when it gets a SizeTag to serialize. Basically you'd have to make the archive not open an array and instead open an object, and then create your own version of loadSize() that would see how big the object is (this would be a Member in rapidjson parlance).


To see the above 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 get:

{
    "filter": {
        "status": "critical",
        "type": "sensor"
    }
}
{
    "filter": {
        "status": "critical",
        "type": "sensor"
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!