我正在从事的项目是一个管理大量自定义硬件设备的C++应用程序。该应用程序为客户端提供了一个套接字/端口接口(如GUI)。每种设备类型都有自己的定义良好的JSON模式,我们可以使用Cereal对这些设备进行序列化。
但是该应用程序还需要解析来自客户端的入站JSON请求。请求的一部分指定设备筛选器参数,大致类似于一个SQL 'WHERE‘子句,其中所有表达式都是ANDed。例如:
"filter": { "type": "sensor", "status": "critical" }
这表示客户端希望对每个具有“临界”状态的“传感器”设备执行操作。表面上看,过滤器参数的C++实现似乎是一个std::map。但是当我们尝试使用Cereal反序列化对象时,它失败了。当我们序列化硬编码的过滤器映射时,它看起来如下所示:
"filter": [
{ "key": "type", "value": "sensor" },
{ "key": "status", "value": "critical" }
]
现在我可以理解为什么Cereal支持这种详细的地图序列化。毕竟,映射的键可以是非字符串类型。但在这种情况下,键是一个字符串。
我并不热衷于重写我们的接口规范,让我们的客户生成明确的、非惯用的JSON来满足Cereal的需求。我对格雷尔不熟悉,我们在这一点上陷入困境。是否有一种方法可以告诉Cereal将此过滤器解析为std::map?或者我问错了。我们是否应该将其他stl容器反序列化到其中?
发布于 2014-03-23 05:40:53
让我先来谈谈为什么麦片比你想要的要长得多。谷物是用来处理任意的序列化档案,并采取一种中间的方法,以满足所有这些。假设键类型比字符串或算术类型更复杂--我们如何以简单的"key" : "value"
方式序列化它?
另外要注意的是,谷物有望成为它读取的任何数据的前身。
话虽如此,你想要的谷类食品是完全有可能的,但有几个障碍:
要克服的最大障碍是,您想要的输入在JSON对象而不是JSON数组中序列化一些未知数量的名称-值对。谷物被设计成在处理可以容纳可变数量元素的容器时使用JSON数组,因为考虑到它使用的底层rapidjson解析器,这是最有意义的。
其次,麦片目前并不指望名字中的名字--值对--实际上会被加载到内存中--它只是将它们作为一种组织工具。
因此,下面是一个完全可行的解决方案(可以让您的问题变得更优雅),只需对谷物进行极小的更改(实际上使用了一个更改,即麦片粥1.1,当前版本为1.0):
将此函数添加到JSONInputArchive
中
//! Retrieves the current node name
/*! @return nullptr if no name exists */
const char * getNodeName() const
{
return itsIteratorStack.back().name();
}
然后,您可以为一对字符串编写std::map
序列化的专门化(或任意无序)。确保将其放在cereal
命名空间中,以便编译器能够找到它。这段代码应该存在于您自己的文件中:
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
这不是最优雅的解决方案,但效果确实很好。我保留了所有的东西,一般都是模板化的,但是我上面写的东西只会在JSON档案上起作用,前提是所做的更改。向XML存档中添加类似的getNodeName()
可能也会让它在那里工作,但显然这对二进制归档没有意义。
为了使它变得清晰,您可能希望将enable_if
放在它所使用的文档中。您还需要修改谷物中的JSON档案,以处理大小不同的JSON对象。要了解如何做到这一点,请查看谷物如何在获得SizeTag
序列化时在归档中设置状态。基本上,您必须使归档文件不打开数组,而是打开对象,然后创建自己版本的loadSize()
,以查看对象有多大(用rapidjson的话说,这将是一个Member
)。
要查看上面的操作,请运行以下代码:
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;
}
你会得到:
{
"filter": {
"status": "critical",
"type": "sensor"
}
}
{
"filter": {
"status": "critical",
"type": "sensor"
}
}
https://stackoverflow.com/questions/22569832
复制相似问题