JSON for Modern C++?可以说是非常摩登的一个C++ json 库了,支持容器化操作(push_back等操作),支持从 stl 容器(vector ,map)创建 json。具体用法就不赘述了,打开文章开头的链接自行查看即可,非常简单。
这篇文章主要是通过自定义树形结构,对这个库进行进一步封装,以期待实现动态的 json 序列化和反序列化。
树形结构擅于组织具有节点关系的动态数据,也适合递归,和 json 的结构不能说很像,只能说非常适合json,所以用它来做与 json 交互的数据结构。
首先创建一个树形结构的基本节点,作为所有节点对象的父类。由于动态树形对象的析构,需要进行链式动态析构(父节点delete子节点),如果在栈上申请对象的话是不行的,所以把析构函数放到 protected 下,这样就不能在栈上创建这个对象了。所有继承这个节点的类,都要把析构函数放到 protected 下。
#include <iostream>
#include <nlohmann/json.hpp>
using namespace nlohmann;
class Node {
protected:
//节点名称
std::string nodeName;
//本节点对应的json
json j;
//子节点们
std::vector<Node*> nodes;
public:
//把本类序列化成json的方法,由子类实现
virtual void toJson(json& j, std::string& errMsg) {
}
//把json反序列化成本类的方法,由子类实现
virtual void toNode(json& j, std::string& errMsg) {
}
//添加子节点
void addNode(Node* node) {
nodes.push_back(node);
}
//把子节点序列化成json
void childNodesToJson(json& j) {
for (Node* node : nodes) {
json jnode;
std::string err;
node->toJson(jnode, err);
j[node->nodeName] = jnode;
}
}
//创建一个子节点类型的节点
template<typename T>
static T* create() {
return new T;
}
//delete本节点
template<typename T>
T* release() {
delete this;
return nullptr;
}
void setNodeName(std::string nodeName) {
this->nodeName = nodeName;
}
protected:
Node() {
}
//由于析构时需要动态的,链式的析构,所以不允许在栈上申请对象
virtual ~Node() {
for (Node* node : nodes) {
node->release<Node>();
}
}
};
为了方便,再写一个代表节点数组的类 NodeArray,来表示json中的数组结构,它既是 Node* 的数组,本身又是一个 Node*
class NodeArray :public std::vector<Node*>, public Node {
public:
NodeArray(std::string name) {
nodeName = name;
}
NodeArray() {}
void toJson(json& j, std::string& errMsg) override{
int size = this->size();
for (int i = 0; i < size; i++) {
json jchild;
(*this)[i]->toJson(jchild, errMsg);
j.push_back(jchild);
}
}
void toNode(json& j, std::string& errMsg) override {
}
protected:
~NodeArray() {
for (Node* node : *this) {
node->release<Node>();
}
}
};
有了这两个基本的类,就可以进行子类化开发了,比如有这样一个json,里面嵌套了一个数组
"ship": {
"cargo": [
{
"good": "FUEL",
"quantity": 23,
"totalVolume": 23
},
{
"good": "FUEL",
"quantity": 23,
"totalVolume": 23
},
{
"good": "FUEL",
"quantity": 23,
"totalVolume": 23
}
],
"class": "MK-I",
"id": "ckon84fo20196vinzktdlvdlv",
"location": "OE-PM-TR",
"manufacturer": "Jackshaw",
"maxCargo": 50,
"plating": 5,
"spaceAvailable": 27,
"speed": 1,
"type": "JW-MK-I",
"weapons": 5,
"x": 21,
"y": -24
}
为了生成一个形如这样的json,来子类化 Node,首先创建一个描述数组中的元素
? ? ? ? ? ? { ? ? ? ? ? ? ? ? "good": "FUEL", ? ? ? ? ? ? ? ? "quantity": 23, ? ? ? ? ? ? ? ? "totalVolume": 23 ? ? ? ? ? ? },
的类,代码如下:
class cargo :public Node {
public:
std::string good = "FUEL";
int quantity = 17;
int totalVolume = 17;
void toJson(json& j, std::string& errMsg) override {
j["good"] = good;
j["quantity"] = quantity;
j["totalVolume"] = totalVolume;
childNodesToJson(j);
}
protected:
~cargo() {
}
};
然后创建一个描述ship的类,代码如下:
class ship:public Node {
public:
NodeArray *cargos = new NodeArray();
MyNode *mynode = new MyNode();
ship(){
cargos->push_back(new cargo());
cargos->push_back(new cargo());
cargos->push_back(new cargo());
cargos->setNodeName("cargos");//To be a child node, NodeArray should have a name
addNode(cargos);//addNode后Node会由父类析构函数析构
addNode(mynode);//addNode后Node会由父类析构函数析构
//否则要在本类析构函数析构
}
void toJson(json& j, std::string& errMsg) override {
json jcargos;
cargos->toJson(jcargos,errMsg);
j["cargos"] = jcargos;
childNodesToJson(j);
j["class"] = "MK-I Jackshaw OE-PM";
}
protected:
~ship() {
}
};
最后写个测试程序测试一下
int test() {
json jship;
std::string err;
ship* myship = new ship();
myship->toJson(jship,err);
myship = myship->release<ship>();
json js;
js["ship"] = jship;
std::cout << js.dump(4);
return 0;
}
输出为
{
"ship": {
"cargos": [
{
"good": "FUEL",
"quantity": 17,
"totalVolume": 17
},
{
"good": "FUEL",
"quantity": 17,
"totalVolume": 17
},
{
"good": "FUEL",
"quantity": 17,
"totalVolume": 17
}
],
"class": "MK-I Jackshaw OE-PM"
}
}
写在最后:
做这个封装的目的在于,对于 json 的每个节点都可能动态变化的情况下,可以灵活的增加一个或大或小,深度或深或浅的预定义节点(或根据预定义节点组合的新节点),而不用修改代码。非常适合编辑 json。
而结构简单的小json,还是使用 JSON for Modern C++ 直接定义更方便。
|