有没有办法使用Cereal / C ++为std :: map指定一个更简单的JSON(解)序列化? [英] Is there a way to specify a simpler JSON (de-)serialization for std::map using Cereal / C++?

查看:1874
本文介绍了有没有办法使用Cereal / C ++为std :: map指定一个更简单的JSON(解)序列化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我工作的项目是一个C ++应用程序,管理大量的自定义硬件设备。该应用程序具有用于客户端的套接字/端口接口(如GUI)。每个设备类型都有自己明确定义的JSON模式,我们可以使用Cereal将它们序列化。



但是应用程序还需要解析来自客户端的入站JSON请求。请求的一部分指定设备过滤器参数,大致类似于SQL的WHERE子句,其中所有表达式都被AND在一起。例如:

 filter:{type:sensor,status:critical} 

这表示客户端希望对处于关键状态的每个传感器设备执行操作。从表面上看,似乎C ++实现的过滤器参数将是一个std :: map。但是当我们尝试使用Cereal来反序列化对象时,它失败了。当我们序列化一个硬编码的过滤器映射,它看起来像这样:

 filter:[
{ key:type,value:sensor},
{key:status,value:critical}
]

现在我可以理解为什么Cereal支持这种详细的地图序列化。毕竟,映射的键可以是非字符串类型。但在这种情况下,键是一个字符串。



我不太热衷于重写我们的界面规范, -idiomatic JSON只是为了满足谷物。我是Cereal的新人,我们坚持这一点。有没有办法告诉Cereal解析这个过滤器作为std :: map?或者也许我要问它的方式。有没有其他的stl容器,我们应该反序列化成

解决方案

让我先谈一谈为什么谷物输出一个更详细的风格一个你可能想要的。谷物被写成与任意序列化档案工作,并采取满足所有的中间地面方法。想象一下,键类型是一个比字符串或算术类型更复杂的东西 - 我们如何将它序列化在一个简单的key:value >

另请注意,谷物预计是其读入的任何数据的原始。






话虽这么说,你想要的是完全可能的谷物,但有一些障碍:



最大的障碍是要克服的事实,在JSON对象而不是JSON数组中序列化一些未知数量的名称/值对。谷物被设计为在处理容纳可变数量元素的容器时使用JSON数组,因为考虑到它使用的底层rapidjson解析器,这是最有意义的。



其次,谷物目前不希望将名称 - 值对实际上加载到内存中 - 它只是将它们用作组织工具。






这样做是一个完全工作的解决方案(可以做得更优雅)你的问题,对谷物非常小的改变(这实际上使用的改变是 slated for cereal 1.1 ,current version is 1.0):



将此函数添加到 JSONInputArchive

  //!检索当前节点名
/ *! @return nullptr如果没有名字存在* /
const char * getNodeName()const
{
return itsIteratorStack.back()。name();
}

然后,您可以编写 std :: map (或无序,任何你喜欢的)。确保将它放在 cereal 命名空间中,以便编译器可以找到它。此代码应存在于您自己的文件中:

 命名空间cereal 
{
//!保存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 & i:map)
ar(cereal :: make_nvp(i.first,i.second));
}

//!加载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)
断点;

std :: string key = namePtr;
std :: string value; ar(value);
hint = map.emplace_hint(hint,std :: move(key),std :: move(value));
}
}
} //命名空间cereal






这不是最优雅的解决方案,但它工作得很好。我留下一切一般模板,但我上面写的只会工作在JSON档案给定的更改。在XML存档中添加一个类似的 getNodeName()可能会让它在那里工作,但显然这对二进制档案没有意义。



为了使这个干净,你会想要 enable_if 围绕它合作的档案。您还需要修改谷物中的JSON归档以使用可变大小的JSON对象。要了解如何做到这一点,看看谷物设置状态在档案中,当它得到一个 SizeTag 序列化。基本上你必须使归档不打开一个数组,而是打开一个对象,然后创建自己的版本的 loadSize(),将看到对象的大小(这将是rapidjson中的会员)。






要查看上述操作,请运行以下代码:

  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>过滤;

ar(CEREAL_NVP(filter));
ar2(CEREAL_NVP(filter));
}

std :: cout<< std :: endl;
return 0;
}

,您将获得:

  {
filter:{
status:critical,
type:sensor
}
}
{
filter:{
status:critical,
type:sensor
}
}


The project I'm working on is a C++ application that manages a large number of custom hardware devices. The app has a socket/port interface for the client (like a GUI). Each device type has its own well-defined JSON schema and we can serialize those with Cereal just fine.

But the app also needs to parse inbound JSON requests from the client. One portion of the request specifies device filter parameters, roughly analogous to a SQL 'WHERE' clause in which all the expressions are ANDed together. E.g.:

"filter": { "type": "sensor", "status": "critical" }

This would indicate that the client wants to perform an operation on every "sensor" device with a "critical" status. On the surface, it seemed like the C++ implementation for the filter parameters would be a std::map. But when we experimented with using Cereal to deserialize the object it failed. And when we serialize a hard-coded filter map it looks like this:

"filter": [
   { "key": "type", "value": "sensor" },
   { "key": "status", "value": "critical" }
]

Now I can understand why Cereal supports this kind of verbose serialization of map. After all, the key of a map could be a non-string type. But in this case the key is a string.

I'm not exactly keen on rewriting our interface spec and making our clients generate clearly non-idiomatic JSON just to satisfy Cereal. I'm new to Cereal and we're stuck on this point. Is there a way to tell Cereal to parse this filter as a std::map? Or maybe I'm asking it the wrong way. Is there some other stl container that we should be deserializing into?

解决方案

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"
    }
}

这篇关于有没有办法使用Cereal / C ++为std :: map指定一个更简单的JSON(解)序列化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆