用于在C++中表示JSON的数据类型

qri*_*kko 13 c++ collections json types c++11

我一直试图想出这个问题一段时间了,也许我只是盯着它看了太长时间?

无论如何,手头的问题是找到一种在C++中表示JSON的好方法,在你阅读之前,请注意我对能够使用它的库不感兴趣,所以我想用原始C或C++(C +) +11很好),没有提升,没有libjson我知道他们,并且由于这个问题范围之外的原因我不能(/不会)添加依赖关系.

现在已经解决了,让我告诉你一些关于这个问题的信息,以及我迄今为止所做的一切.

问题是找到一种在C++中表示JSON的好方法,这有点问题的原因是JSON是超松散类型的,而C++实际上是很难输入的.考虑JSON一秒钟,JSON真正能够打字吗?

  • 号码(例如423.1415)
  • 字符串(例如"my string")
  • 数组(例如[],或[1,3.1415,"my string])
  • 对象(例如{}{42, 3.1415, "my string", [], [1,3.1415, "my string]}

那么这意味着有两个"原始"类型,NumberString,以及两个容器类型ArrayObject.原始类型是相当直接的,而容器类型在C/C++中变得棘手,因为它们可以并且可能包含不同类型的元素,因此语言中的任何内置类型都不够,数组不能保持不同类型的元素.这也适用于STL类型(列表,向量,数组等),(除非它们具有多态相等).

所以JSON中的任何容器都可以容纳任何类型的json类型,这几乎就是它的全部内容.

我原型,或尝试过以及为什么它不能工作 我的第一个天真的想法是只使用模板,所以我设置了一个json-object或json-node类型,然后使用模板来决定它里面有什么,所以它会然后有一个像这样的结构:

template <class T>
class JSONNode {
    const char *key;
    T value;
}
Run Code Online (Sandbox Code Playgroud)

虽然这似乎很有希望,但是当我开始使用它时,我意识到当我尝试将节点命令为容器类型(例如数组,矢量,unordered_map等)时,我遇到了麻烦,因为他们仍然想知道JSONNode的类型!如果一个节点被定义为JSONNode<int>另一个节点,JSONNode<float>那么将它们放在容器中会有问题.

所以我走过去了,无论如何我都不是那么有兴趣将它们保存在容器中,我很乐意让它们自我意识或者称它为什么,即广告指向下一个节点,但又一次找出节点的类型变得棘手,而这就是我开始思考多态的时候.

多态性 让我们只创建一个虚拟JSONNode并实现一个JSONNumberNode, JSONStringNode, JSONArrayNodeJSONObjectNode类型,它们将很好地适应我可能想要它们的任何容器,使用多态来让它们都是JSONNodes.

代码的示例可能已到位.

class JSONNode {
public:
    const char *key;
    //?? typed value, can't set a type
};

class JSONNumberNode : public JSONNode { 
public:
    int value;
}

class JSONStringNode : public JSONNode {
public:
    const char *value;
}
Run Code Online (Sandbox Code Playgroud)

起初我认为这是要走的路.然而,当我开始考虑如何处理值 - 部分时,我意识到我无法访问该值,即使我写了一个特定的函数来检索值,它会返回什么?

所以我确定我的对象具有不同的类型值,但是如果没有首先转换为正确的类型我就无法真正访问它们,所以我可以做一个dynamic_cast<JSONStringNode>(some_node);,但我怎么知道将它转换为什么?RTTI?好吧,我觉得在这一点上变得有点复杂,我想我可能能够使用typeof或decltype来搞清楚要对它进行类型转换,但是还没有成功.

POD类型 所以我尝试了不同的东西,我想这可能是因为我可以用pod-way来实际做到这一点.然后我会设置value部分,void *并尝试union跟踪类型.但是我遇到了与现有问题相同的问题,即如何将数据转换为类型.

我觉得有必要回答这个问题为什么我没有深入研究我尝试使用POD的内容.

因此,如果有人知道如何在C++中表示JSON的智能解决方案,我会非常感激.

cib*_*cib 8

你的最后两个解决方案都可行.你在他们两个中的问题似乎是提取实际值,所以让我们看看例子.我将介绍POD的想法,原因很简单,使用多态会确实需要RTTI,恕我直言.

JSON:

{
    "foo":5
}
Run Code Online (Sandbox Code Playgroud)

你加载这个JSON文件,你将得到的只是你的POD"包装器".

json_wrapper wrapper = load_file("example.json");
Run Code Online (Sandbox Code Playgroud)

现在假设您加载的JSON节点是一个JSON对象.您现在必须处理两种情况:它是一个对象,还是不是.如果不是,您可能最终会处于错误状态,因此可以使用异常.但是你如何提取物体呢?好吧,只需一个函数调用.

try {
    JsonObject root = wrapper.as_object();
} catch(JSONReadException e) {
    std::cerr << "Something went wrong!" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

现在,如果包装的JSON节点wrapper确实是一个JSON对象,则可以try {使用对象的任何内容继续执行该块.同时,如果JSON"格式错误",你就会进入catch() {阻止.

在内部,你会实现这样的事情:

class JsonWrapper {
    enum NodeType {
       Object,
       Number,
       ...
    };

    NodeType type;

    union {
        JsonObject object;
        double number
    };

    JsonObject as_object() {
        if(type != Object) {
            throw new JSONReadException;
        } else {
            return this->object;
        }
    }
Run Code Online (Sandbox Code Playgroud)


Pap*_*ter 8

我认为你的最后一个方法是正确的方向,但我认为它需要改变一些概念设计.

在迄今为止我工作的所有JSON解析器中,选择容器类型的决定是在用户端而不是在解析器端,我认为这是一个明智的决定,为什么?假设您有一个包含字符串格式的数字的节点:

{
    "mambo_number": "5"
}
Run Code Online (Sandbox Code Playgroud)

您不知道用户是想要将值检索为字符串还是数字.所以,我会指出的是,JSONNumberNodeJSONStringNode不适合的最佳方法.我的建议是创建用于保存对象,数组和原始值的节点.

所有这些节点都将根据其主要类型包含标签(名称)和嵌套对象列表:

  • JSONNode:基本节点类,包含密钥和节点类型.
  • JSONValueNode:用于管理和包含原始值,像上面列出的曼波nº5节点类型,它会提供一些功能来读取它的值,比如value_as_string(),value_as_int(),value_as_long(),迄今...
  • JSONArrayNode:管理JSON数组并包含JSONNode索引的可访问性的节点类型.
  • JSONObjectNode:管理JSON对象并包含JSONNode按名称可访问的节点类型.

我不知道这个想法是否有详细记录,让我们看一些例子:

例1

{
    "name": "murray",
    "birthYear": 1980
}
Run Code Online (Sandbox Code Playgroud)

上面的JSON将是一个未命名的根JSONObjectNode,其中包含JSONValueNode带有标签name和的两个s birthYear.

例2

{
    "name": "murray",
    "birthYear": 1980,
    "fibonacci": [1, 1, 2, 3, 5, 8, 13, 21]
}
Run Code Online (Sandbox Code Playgroud)

上面的JSON将是一个JSONObjectNode包含两个JSONValueNodes和一个的未命名根JSONArrayNode.在JSONArrayNode将包含8无名JSONObjectNode与斐波那契数序列的8个第一值秒.

例3

{
    "person": { "name": "Fibonacci", "sex": "male" },
    "fibonacci": [1, 1, 2, 3, 5, 8, 13, 21]
}
Run Code Online (Sandbox Code Playgroud)

上面的JSON将是一个无名根JSONObjectNode包含JSONObjectNode具有两个JSONValueNodes的标签namesex和一个JSONArrayNode.

例4

{
    "random_stuff": [ { "name": "Fibonacci", "sex": "male" }, "random", 9],
    "fibonacci": [1, 1, 2, 3, 5, 8, 13, 21]
}
Run Code Online (Sandbox Code Playgroud)

上面的JSON将是一个未命名的根JSONObjectNode,其中包含两个JSONArrayNode,第一个,标记为random_stuff将包含3个未命名JSONValueNode的类型JSONObjectNode,JSONValueNode并且JSONValueNode按照出现的顺序,第二个JSONArrayNode是之前评论过的斐波纳契序列.

履行

我将面对节点实现的方式如下:

基节点将通过成员知道它自己的类型(值节点,数组节点或对象节点)type,type由派生类在构造时提供值.

enum class node_type : char {
    value,
    array,
    object
}

class JSONNode {
public:
    JSONNode(const std::string &k, node_type t) : node_type(t) {}
    node_type GetType() { ... }
    // ... more functions, like GetKey()
private:
    std::string key;
    const node_type type;
};
Run Code Online (Sandbox Code Playgroud)

派生类必须向基础提供构造时间中节点的类型,值节点向用户提供将存储值转换为用户请求的类型:

class JSONValueNode : JSONNode {
public:
    JSONValueNode(const std::string &k, const std::string &v) :
        JSONNode(k, node_type::value) {} // <--- notice the node_type::value
    std::string as_string() { ... }
    int as_int() { ... }
    // ... more functions
private:
    std::string value;
}
Run Code Online (Sandbox Code Playgroud)

阵列节点必须提供operator[]以便将其用作数组; 实现一些迭代器是值得的.内部存储的值std::vector(选择您认为最适合此目的的容器)将是JSONNode.

class JSONArrayNode : JSONNode {
public:
    JSONArrayNode(const std::string &k, const std::string &v) :
        JSONNode(k, node_type::array) {} // <--- notice the node_type::array
    const JSONObjectNode &operator[](int index) { ... }
    // ... more functions
private:
    std::vector<JSONNode> values;
}
Run Code Online (Sandbox Code Playgroud)

我认为Object Node必须提供operator[]带字符串的输入,因为在C++中我们不能复制JSON node.field访问器,实现一些迭代器是值得的.

class JSONObjectNode : JSONNode {
public:
    JSONObjectNode(const std::string &k, const std::string &v) :
        JSONNode(k, node_type::object) {} // <--- notice the node_type::object
    const JSONObjectNode &operator[](const std::string &key) { ... }
    // ... more functions
private:
    std::vector<JSONNode> values;
}
Run Code Online (Sandbox Code Playgroud)

用法

假设所有节点都具有所有必需的功能,使用我的aproach的想法将是:

JSONNode root = parse_json(file);

for (auto &node : root)
{
    std::cout << "Processing node type " << node.GetType()
              << " named " << node.GetKey() << '\n';

    switch (node.GetType())
    {
        case node_type::value:
            // knowing the derived type we can call static_cast
            // instead of dynamic_cast...
            JSONValueNode &v = static_cast<JSONValueNode>(node);

            // read values, do stuff with values
            break;

        case node_type::array:
            JSONArrayNode &a = static_cast<JSONArrayNode>(node);

            // iterate through all the nodes on the array
            // check what type are each one and read its values
            // or iterate them (if they're arrays or objects)
            auto t = a[100].GetType();
            break;

        case node_type::object:
            JSONArrayNode &o = static_cast<JSONObjectNode>(node);

            // iterate through all the nodes on the object
            // or get them by it's name check what type are
            // each one and read its values or iterate them.
            auto t = o["foo"].GetType();
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记

我不会使用Json-Whatever-Node命名约定,我喜欢将所有内容放入命名空间并使用较短的名称; 在名称空间范围之外,名称非常易读且不可取:

namespace MyJSON {
class Node;
class Value : Node;
class Array : Node;
class Object : Node;

Object o; // Quite easy, short and straightforward.

}

MyJSON::Node n;  // Quite readable, isn't it?
MyJSON::Value v;
Run Code Online (Sandbox Code Playgroud)

我认为在无效访问的情况下创建每个对象的null版本是值得的:

// instances of null objects
static const MyJSON::Value null_value( ... );
static const MyJSON::Array null_array( ... );
static const MyJSON::Object null_object( ... );

if (rootNode["nonexistent object"] == null_object)
{
    // do something
}
Run Code Online (Sandbox Code Playgroud)

前提是:返回null对象类型访问对象节点中不存在的子对象或对数组节点的超出范围的访问.

希望能帮助到你.