编写JSON解释器的设计问题

时间:2012-09-25 06:21:25

标签: c++ design-patterns c++11

我决定用c ++编写一个json解释器来练习。理想情况下,我希望能够在地图,向量和相关值类型的树状容器中进行设置,以便我可以按照json实际构造的方式进行访问。例如。给出以下JSON示例(来自json.org):

JSON
{
"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}}


C++
jsonobject["menu"]["id"] // returns std::string "file"
jsonobject["menu"]["popup"]["menuitem"] // returns std::vector of std::maps
jsonobject["menu"]["popup"]["menuitem"][0]["value"] // returns std::string "New"

在上面的例子中,我的第一个问题来自于在容器内部使用混合类型。例如,在上面的json“menu”中将是一个std :: map,但我不能拥有键“id”和“popup”,因为一个返回一个字符串,另一个返回一个矢量。

为了解决这个问题,我决定创建一个继承自无类型基类的包装器模板类。假设这会给出值的多态访问。问题是我做不到。这里有一些代码可以显示我到目前为止的内容:

#include <string>
#include <map>
#include <vector>

class NodeBase {};

template <typename T>
class Node : public BaseNode {};

typedef std::map<std::string, BaseNode*> JSONObject;
template <>
class Node<JSONObject> : public BaseNode {
  public:
    JSONObject value;
    BaseNode* operator[](const std::string key){(value.find(key) != value.end) ? return value[key] : return nullptr}
};

typedef std::vector<BaseNode*> JSONArray;
template <>
class Node<JSONArray> : public BaseNode {
  public:
    JSONArray value;
    BaseNode* operator[](const uint index) {(index < value.size()) ? return value[index] : return nullptr}
};

template <typename T>
class Node : public BaseNode {
  public:
    T value;
};

class RootNode {
  Node<JSONObject> value;
};

int main(void) {
  RootNode root;
  root.insert(std::pair<std::string, BaseNode*>("menu", new Node<JSONObject>)
  // problem!
  // cannot use following code, because BaseNode* does not have access to value :'<
  root["menu"].insert(..)
}

所以我想我的问题是,我该如何做到这一点?我是否走在正确的道路上却无法从经验不足中看到解决方案,或者这种设计是否与C ++完全不兼容?

1 个答案:

答案 0 :(得分:3)

有趣的类,但我最近使用boost :: property_three来解析json并且很高兴我避免重新发明wheel xD你的库可以作为轻量级解析器工作,所以我对这项研究感兴趣。

我认为这个设计不是很好,使用库的人可能不知道某些值包含什么:哈希或数组。这可能会导致dynamic_cast超载,这是一个相对繁重的操作。结果,您的图书馆失去了“轻量级”属性。

我建议使用一种对象类型表示法,就像所有Get方法返回Node*一样,但Node*可以回答问题isArray()isHash()而无需大量操作喜欢动态演员。

接下来,Node<JSONObject>Node<JSONArray>应该具有从BaseNode继承的相同接口:BaseNode* operator[](const std::string key),但Node<JSONArray>能够接收int密钥通过operator[]重载。这会缩小API并允许用户使用int索引,如果他知道它是数组。但它不是灵丹妙药,你应该做所谓的“图书馆设计”。

在全球范围内,我建议您不要立即编写代码。编写一些类层次结构模式,编写一些用例;看看这些用例如何适应模式。一般来说,编写这样的库不是一项简单的任务,而“设计”错误可能会使您的库在将来无法使用。所以,选择明智=)Goodluck!