我认为你的最后一种方法是朝着正确的方向前进,但我认为它需要改变一些概念设计。
到目前为止,在我工作过的所有 JSON 解析器中,选择容器类型的决定是在用户端而不是在解析器端,我认为这是一个明智的决定,为什么?假设您有一个包含字符串格式数字的节点:
{
"mambo_number": "5"
}
您不知道用户是否希望将值作为字符串或数字检索。所以,我会指出JSONNumberNode
andJSONStringNode
不适合最好的方法。我的建议是创建用于保存对象、数组和原始值的节点。
所有这些节点将根据其主要类型包含一个标签(名称)和一个嵌套对象列表:
JSONNode
:基节点类,其中包含节点的键和类型。
JSONValueNode
: 管理和包含原始值的节点类型,如上面列出的 Mambo nº5,它会提供一些函数来读取其值,如value_as_string()
, value_as_int()
, value_as_long()
, 等等...
JSONArrayNode
: 管理 JSON 数组并包含JSONNode
s 可访问对象的节点类型。
JSONObjectNode
: 管理 JSON 对象并包含JSONNode
按名称访问的节点类型。
我不知道这个想法是否有据可查,让我们看一些例子:
示例 1
{
"name": "murray",
"birthYear": 1980
}
上面的 JSON 将是一个未命名的根JSONObjectNode
,其中包含两个JSONValueNode
带有标签的 sname
和birthYear
。
示例 2
{
"name": "murray",
"birthYear": 1980,
"fibonacci": [1, 1, 2, 3, 5, 8, 13, 21]
}
上面的 JSON 将是一个未命名的根JSONObjectNode
,其中包含两个JSONValueNode
s 和一个JSONArrayNode
。将JSONArrayNode
包含 8 个未命名JSONObjectNode
的 s 以及斐波那契数列的 8 个第一个值。
示例 3
{
"person": { "name": "Fibonacci", "sex": "male" },
"fibonacci": [1, 1, 2, 3, 5, 8, 13, 21]
}
上面的 JSON 将是一个未命名的根JSONObjectNode
,其中包含 aJSONObjectNode
和两个JSONValueNode
带有标签的 sname
和sex
一个JSONArrayNode
。
示例 4
{
"random_stuff": [ { "name": "Fibonacci", "sex": "male" }, "random", 9],
"fibonacci": [1, 1, 2, 3, 5, 8, 13, 21]
}
上面的 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;
};
派生类必须在构造时向基类提供节点的类型,值节点向用户提供将存储的值转换为用户请求的类型:
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;
}
Array Node 必须提供operator[]
才能将其用作数组;实现一些迭代器是值得的。内部的存储值std::vector
(选择您认为最适合此目的的容器)将是JSONNode
's.
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;
}
我认为对象节点必须提供operator[]
字符串输入,因为在 C++ 中我们不能复制 JSONnode.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;
}
用法
假设所有节点都具有所有必需的功能,我的方法的使用思路将是:
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;
}
}
笔记
我不会使用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;
我认为值得为每个对象创建空版本以在无效访问的情况下提供:
// 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
}
前提是:在访问对象节点中不存在的子对象或越界访问数组节点的情况下,返回空对象类型。
希望能帮助到你。