首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >是否有一种方法可以使用Cereal / C++为std::map指定更简单的JSON (反)序列化?

是否有一种方法可以使用Cereal / C++为std::map指定更简单的JSON (反)序列化?
EN

Stack Overflow用户
提问于 2014-03-21 21:26:40
回答 1查看 6.2K关注 0票数 10

我正在从事的项目是一个管理大量自定义硬件设备的C++应用程序。该应用程序为客户端提供了一个套接字/端口接口(如GUI)。每种设备类型都有自己的定义良好的JSON模式,我们可以使用Cereal对这些设备进行序列化。

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

代码语言:javascript
运行
复制
"filter": { "type": "sensor", "status": "critical" }

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

代码语言:javascript
运行
复制
"filter": [
   { "key": "type", "value": "sensor" },
   { "key": "status", "value": "critical" }
]

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

我并不热衷于重写我们的接口规范,让我们的客户生成明确的、非惯用的JSON来满足Cereal的需求。我对格雷尔不熟悉,我们在这一点上陷入困境。是否有一种方法可以告诉Cereal将此过滤器解析为std::map?或者我问错了。我们是否应该将其他stl容器反序列化到其中?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-03-23 05:40:53

让我先来谈谈为什么麦片比你想要的要长得多。谷物是用来处理任意的序列化档案,并采取一种中间的方法,以满足所有这些。假设键类型比字符串或算术类型更复杂--我们如何以简单的"key" : "value"方式序列化它?

另外要注意的是,谷物有望成为它读取的任何数据的前身。

话虽如此,你想要的谷类食品是完全有可能的,但有几个障碍:

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

其次,麦片目前并不指望名字中的名字--值对--实际上会被加载到内存中--它只是将它们作为一种组织工具。

因此,下面是一个完全可行的解决方案(可以让您的问题变得更优雅),只需对谷物进行极小的更改(实际上使用了一个更改,即麦片粥1.1,当前版本为1.0):

将此函数添加到JSONInputArchive

代码语言:javascript
运行
复制
//! Retrieves the current node name
/*! @return nullptr if no name exists */
const char * getNodeName() const
{
  return itsIteratorStack.back().name();
}

然后,您可以为一对字符串编写std::map序列化的专门化(或任意无序)。确保将其放在cereal命名空间中,以便编译器能够找到它。这段代码应该存在于您自己的文件中:

代码语言:javascript
运行
复制
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 )。

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

代码语言:javascript
运行
复制
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;
}

你会得到:

代码语言:javascript
运行
复制
{
    "filter": {
        "status": "critical",
        "type": "sensor"
    }
}
{
    "filter": {
        "status": "critical",
        "type": "sensor"
    }
}
票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/22569832

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档