C++轻量级对象Json序列化与反射框架

一月 02, 2024 [C/C++] #C++ #obsidian #协议与标准

C++轻量级对象Json序列化与反射框架


出处:https://zhuanlan.zhihu.com/p/422206102 - 知乎专栏


代码已开源:

https://github.com/shijunfeng00/CppJson

深度炼丹之余,来折腾下C++

最近花了点时间实现了一个简单的反射、序列化与反序列化的一套代码

不过只是自己实现的玩具级应用,没有做太多的优化和Debug,对std的支持相当有限,但我觉得这个思路应该是值得推广的,C++以高性能著称,但是有时候我们也希望尽可能简单的实现某些功能,比如反射、序列化、反序列化。

一、项目背景

C++没有原生的反射与序列化,至少从语言层面来说是暂时不支持的 很多时候是需要自己手动去写反射相关的代码 自己实现方案很多,也有很多现成的库可以使用 但是很多代码用起来都异常繁琐,包含各种模板以及宏,比如类似这样的

Ubp.a:99 行实现 C++ 简单反射

"全球最强" | C++ 动态反射库

int main(){
    Reflection<Point>::Instance()
        .Regist(&Point::x,"x")
        .Regist(&Point::y,"y")
        .Regist(&Point::Add,"Add");
    Point p;
    Reflection<Point>::Instance().Var<float>("x").Of(p)=2.f;
    Reflection<Point>::Instance().Var<float>("y").Of(p)=3.f;
    Point q=Reflection<Point>::Instance().call<Point>("Add",p,1.f);
    for(auto&nv:Reflection<Point>::Instance().Vars())
        cout<<nv.first<<":"<<nv.second->As<float>().Of(p)
}

以及像这样用宏来定义的

struct test_type0{
DEF_FIELD_BEGIN(test_type0)
private:
    DEF_FIELD(int, x)
public:
    DEF_FIELD(std::string, y)
DEF_FIELD_END
};

struct test_type1{
DEF_FIELD_BEGIN(test_type1)
    DEF_FIELD(test_type0, z)
    DEF_FIELD(std::string, w)
DEF_FIELD_END
};

总之我觉得无论是使用也好,还是注册也好,用起来都是很繁琐的。 所以我自然希望实现一个类似于Tensorflow的自动微分那样,只要写好前向传播代码,自动微分就已经自动生成好了。 我希望当我写完CPP的代码,对应的struct/class的序列化、反序列化、反射就已经自动完成了所有注册, 也不用单独为每个class写一个序列化与反序列化,一切东西都用起来足够"原生"。 所以我的代码尽可能简化了代码使用的这一过程,具体实现其实也不复杂,就是各种类型匹配和递归调用的问题。 我将会在后文中给出充分的示例。

总之就是我希望能有一个更简洁的方案,能够[自动]完成反射、序列化、反序列化。就像深度学习里面的自动微分框架一样,只要我构建好代码,就已经自动构建好了微分运算。只要我写好了结构体,就自动构建好了对应的序列化、反序列化方法。

使用起来就像这样,非常的简洁,简洁得像是在写python

Serializable::Regist<Point>();                //注册
Point p=Reflectable::get_instance("Point");   //创建实例
Reflectable::set_field("x",2.0f);             //修改属性值
Reflectable::set_field("y",3.0f);
string json=Serializable::dumps(p);           //序列化为json字符串
Point&q=Serializable::loads(json);            //反序列化还原对象

当然可序列化的前提就是可反射的,所以Serializable是Reflectable的子类

二、相关限制

原本反射只是为了序列化和反序列化,只能支持属性值,现在已经支持成员函数了,不过并未处理继承与派生,以及函数重载的问题,所以功能上还比较弱鸡。序列化上可以支持单继承

目前我的代码支持属性满足以下类型条件的class的序列化与反序列化

(1).C++基本类型(int,float,char,...)

(2).含有迭代器的容器(std::vector,std::list,std::deque,...)

(3).容器适配器(std::map,std::set,std::unordered_map,std::unordered_set,...)

(4).std::tuple和std::pair

(5).其他正确实现了get_config以及调用Serializable::regist<T>注册的类

(6).数组(int a[15]),std::array<int,15>

(7).上述类型的组合及其指针(std::vector[std::pair**int\*,float\*](int%5C*,float%5C*%5D(std::pair%3Cint%5C*,float%5C*))** ),可以正确处理空指针,空指针将被序列化为"null"。

(8).派生类的序列化与反序列化(暂没考虑多继承虚继承的问题)

反射的话还比较弱鸡,属性值上理论上支持一切非静态属性的反射

成员函数上也只支持非静态的成员函数,不支持继承和虚函数,至少我没有刻意去实现,说不定代码意外的自己已经能支持了呢(逃

三、简洁的注册与接口使用

首先看一个简单的例子

struct Node
{
    int x;
    float y;
    std::string z;
    int add(int x,int y)
    {
        return x+y;
    }
    std::string getName()
    {
        return z;
    }
};

现在我想要这个类支持反射、序列化和反序列化 只需要做出如下修改:

(1). 实现Config::get_config方法

Config get_config()const
    {
        Config config=Serializable::get_config(this);
        config.update({
            {"x",x},
            {"y",y},
            {"z",z},
            {"add",add},
            {"getName",getName}
        });
        return config;
    }

(2).在main函数中使用Serializable::Regist<Node>()完成注册

完成简单注册后,然后就可以使用如下方法支持属性的反射,序列化,反序列化

int main()
{
    Serializable::Regist<Node>();
    void*object=Reflectable::get_instance("Node");                        //创建实例

    Reflectable::set_field<int>(object,"Node","x",4);                     //通过字符串名称修改成员变量
    Reflectable::set_field<float>(object,"Node","y",5);
    Reflectable::set_field<std::string>(object,"Node","z","test");
    int field_x=Reflectable::get_field<int>(object,"Node","x");           //通过字符串名称得到值
    float field_y=Reflectable::get_field<float>(object,"Node","y");
    string field_z=Reflectable::get_field<string>(object,"Node","z");

    cout<<field_x<<" "<<(*(Node*)object).x<<endl;                         //正常访问成员变量,对比结果
    cout<<field_y<<" "<<(*(Node*)object).y<<endl;
    cout<<field_z<<" "<<(*(Node*)object).z<<endl;
    /*序列化与反序列化*/

    std::string json=Serializable::dumps(*(Node*)object);                 //序列化
    cout<<json<<endl;
    Node b=Serializable::loads<Node>(json);                               //反序列化
    /*正常访问*/
    cout<<b.x<<endl;                                                      //正常访问成员变量
    cout<<b.y<<endl;
    cout<<b.z<<endl;
    /*成员函数反射*/
    cout<<Reflectable::get_method<int>(b,"add",5,6)<<endl;                //通过字符串名称访问成员函数
    cout<<Reflectable::get_method<string>(b,"getName")<<endl;
}

运行代码得到如下结果:

四、序列化、反序列化代码示例

序列化,反序列化关键方法只有两个 Serializable::dumps和Serializable::loads 分别对应序列化与反序列化,其中反序列化需要显式的给出被反序列化的对象类型 如果不给的话将会返回一个void*指针,如果给了参数,会返回一个引用 如果不想调用额外的复制构造函数,可以使用auto&object=Serializable::loads(json_string)

(1).trie字典树的序列化与反序列化

首先我们实现一棵朴素的trie树,没经过任何优化,这里面包括了std::vector,Node*,bool多种类型需要处理。

#include<iostream>
#include<fstream>
#include<vector>
using namespace std;
struct Node
{
	Node():sons(26,nullptr),flag(false){};
	~Node();
	std::vector<Node*>sons;
	bool flag;
};
class Trie
{
public:
	Trie():root(new Node){}
	void insert(const std::string&str);
	void print();
private:
	void __print(Node*root,std::string&str);
	Node*root;
};
Node::~Node()
{
	for(auto&son:sons)
		if(son!=nullptr)
			delete son;
}
void Trie::print()
{
	std::string tmp;
	__print(root,tmp);
}
void Trie::insert(const std::string&str)
{
	Node*root=this->root;
	for(auto&it:str)
	{
		if(root->sons[it-'a']==nullptr)
			root->sons[it-'a']=new Node();
		root=root->sons[it-'a'];
	}
	root->flag=true;
}
void Trie::__print(Node*root,std::string&str)
{
	if(root->flag>0)
	{
		cout<<str<<endl;
	}
	for(unsigned i=0;i<root->sons.size();i++)
	{
		if(root->sons[i]!=nullptr)
		{
			str.push_back(i+'a');
			__print(root->sons[i],str);
			str.pop_back();
		}
	}
}
int main()
{
	ifstream fin("test.json",ios::in);
	ofstream fout("test.json",ios::out);
	Trie trie;
	trie.insert("ababa");
	trie.insert("abab");
	trie.insert("xisa");
	trie.insert("cpp");                                        //构建一棵字典树
	trie.insert("cest");
	trie.insert("test");
	trie.insert("tet");
	trie.print();                                              //打印trie树所有单词
}

程序输出了所有单词

现在如果我需要把这颗保存下来,日后再还原,就需要序列化与反序列化

如果用我写的这份代码来做,事情将变得非常简单,我们来看看修改后的代码:

#include"lang/serializable.h"
#include<iostream>
#include<fstream>
using namespace std;
struct Node
{
	Node():sons(26,nullptr),flag(false){};
	Config get_config()const  //变化1
	{
		Config config=Serializable::get_config(this);
		config.update({
			{"sons",sons},
			{"flag",flag}
		});
		return config;
	}
	~Node();
	std::vector<Node*>sons;
	bool flag;
};
class Trie
{
public:
	Trie():root(new Node){}
	Config get_config()const
	{
		Config config=Serializable::get_config(this);
		config.update({
			{"root",root}
		});
		return config;
	}
	void insert(const std::string&str);
	void print();
private:
	void __print(Node*root,std::string&str);
	Node*root;
};
Node::~Node()
{
	for(auto&son:sons)
		if(son!=nullptr)
			delete son;
}
void Trie::print()
{
	std::string tmp;
	__print(root,tmp);
}
void Trie::insert(const std::string&str)
{
	Node*root=this->root;
	for(auto&it:str)
	{
		if(root->sons[it-'a']==nullptr)
			root->sons[it-'a']=new Node();
		root=root->sons[it-'a'];
	}
	root->flag=true;
}
void Trie::__print(Node*root,std::string&str)
{
	if(root->flag>0)
	{
		cout<<str<<endl;
	}
	for(unsigned i=0;i<root->sons.size();i++)
	{
		if(root->sons[i]!=nullptr)
		{
			str.push_back(i+'a');
			__print(root->sons[i],str);
			str.pop_back();
		}
	}
}
int main()
{
	Serializable::Regist<Node,Trie>();                         //变化2
	ifstream fin("test.json",ios::in);
	ofstream fout("test.json",ios::out);
	Trie trie;
	trie.insert("ababa");
	trie.insert("abab");
	trie.insert("xisa");
	trie.insert("cpp");                                        //构建一棵字典树
	trie.insert("cest");
	trie.insert("test");
	trie.insert("tet");
	std::string json=Serializable::dumps(trie);                //序列化

	fout<<json;                                                //保存到文件
	fout.close();

	std::getline(fin,json);                                    //读取文件(一行json数据)
	fin.close();
	std::cout<<"序列化后的json字符串\n"<<json<<endl<<endl;
	Trie&new_trie=Serializable::loads<Trie>(json);             //反序列化
	new_trie.print();                                          //打印trie树所有单词
}

输出结果如下:

可以看到这颗字典树被正确还原了,结果与之前保持一致

(2).序列化一颗二叉树

struct Node:public Serializable
{
	Node(Node*lson,Node*rson,std::function<int(Node*,Node*)>eval):value(eval(lson,rson)),lson(lson),rson(rson){}
	Node(int value=0):value(value),lson(nullptr),rson(nullptr){}
	~Node()
	{
		if(lson!=nullptr)
			delete lson;
		if(rson!=nullptr)
			delete rson;
	}
	Config get_config()const //变化1
	{
		Config config=Serializable::get_config(this);
		config.update({
			{"value",value},
			{"lson",lson},
			{"rson",rson}
		});
		return config;
	}
	int value;
	Node*lson;
	Node*rson;
};
int main()
{
	Serializable::Regist<Node>(); //变化2
	ifstream fin("test.json",ios::in);
	ofstream fout("test.json",ios::out);
	auto add=Node*x,Node*y{return x->value+y->value;};
	auto sub=Node*x,Node*y{return x->value-y->value;};
	auto mul=Node*x,Node*y{return x->value*y->value;};
	Node*x=new Node(5);
	Node*y=new Node(6);
	Node*z=new Node(x,y,add); //z=x+y=11
	z=new Node(x,z,mul);      //z=x*z=55
	z=new Node(z,y,sub);      //z=z-y=49
	std::string json=Serializable::dumps(*z);  //序列化
	fout<<json;
	fout.close();

	getline(fin,json);
	fin.close();
	cout<<"json string:\n"<<json<<endl<<endl;
	Node*root=(Node*)Serializable::loads(json); //反序列化
	cout<<root->value;
}

这次序列化出来的结果就比较直观

更进一步的,我们可以尝试用python的json库来处理,也是可以正常加载的。

(4).std::tuple和std::pair

除了标准容器,我也对这两个提供了支持

struct Node
{
	std::list<std::tuple<int,float,std::string>>x;
	std::pair<float,int>y;
	Config get_config()const
	{
		Config config=Serializable::get_config(this);
		config.update({
			{"x",x},
			{"y",y}
		});
		return config;
	}
};
int main()
{
	Serializable::Regist<Node>();
	Node a;
	a.x={{1,2.1,"first"},{2,3,"second"}};
	a.y={3.4,2};
	string json=Serializable::dumps(a);
	cout<<json<<endl;
	Node b=Serializable::loads<Node>(json);
	cout<<b.get_config().serialized_to_string(true)<<endl;
	cout<<get<2>(*b.x.begin())<<endl;
}

程序运行结果:

(5)数组

众所周知C/C++的数组在传参过程中是会退化为指针的,但是实际数组和指针是两种不同类型

int*x=new int[15];
int y[15];
cout<<std::is_same<decltype(x),decltype(y)>::value<<endl;
cout<<std::is_pointer<decltype(x)>::value<<endl;
cout<<std::is_pointer<decltype(y)>::value<<endl;
/*
输出:
0
1
0
*/

但由于我的函数是模板函数,所以传参过程中避免了退化,因此就可以支持原生数组

template<typename T> //T=int[15],长度信息被保留,没有发生退化
ConfigPair::ConfigPair(const std::string&name,const T&object):
	key(name),
	value(ConfigPair::get_config_string<T>(object)),
	type(GET_TYPE_NAME(T)),
	address((std::size_t)&object){}

写段代码测试一下

struct Node
{
	Node()
	{
		std::memset(x,0,sizeof(x));
	}
	Node(const Node&)=delete;
	Config get_config()const
	{
		Config config=Serializable::get_config(this);
		config.update({
			{"x",x},
		});
		return config;
	}
	int x[15];
};
int main()
{
	Serializable::Regist<Node>();
	Node a,b;
	for(int i=0;i<15;i++)
		a.x[i]=i;
	cout<<Serializable::dumps(a)<<endl;
	cout<<Serializable::dumps(b)<<endl;
	std::string json=Serializable::dumps(a);
	b=Serializable::loads<Node>(json);
	cout<<Serializable::dumps(b)<<endl;
}

程序运行结果:

(6)复杂结构体的序列化与反序列化

算是对上面说的做一个总结吧。。。

#include"lang/serializable.h"
#include<iostream>
#include<fstream>
#include<cstring>
struct Node
{
	Node()
	{
		std::memset(z,0,sizeof(z));
		t={1,2,3,4};
	}
	Config get_config()const
	{
		Config config=Serializable::get_config(this);
		config.update({
			{"x",x},
			{"y",y},
			{"z",z},
			{"t",t}
		});
		return config;
	}
	std::tuple<float,std::pair<int,std::string>>x;
	std::vector<Node*>y;
	int z[3];
	std::array<int,4>t;
};
using namespace std;
int main()
{
	Serializable::Regist<Node>();                                                   //注册
	Node a=*(Node*)Reflectable::get_instance("Node");                               //创建实例
	Reflectable::set_field(a,"x",make_tuple(3.2f,make_pair(5,string{"test"})));     //通过名称修改属性
	Reflectable::set_field(a,"y",std::vector<Node*>{new Node,nullptr});
	int*z=(int*)Reflectable::get_field(a,"z");                                      //获得属性,并进行修改
	z[0]=2021,z[1]=10,z[2]=18;
	a.t[0]=2021,a.t[1]=10,a.t[2]=19,a.t[3]=10;
	std::string json=Serializable::dumps(a);                                        //序列化为json格式的字符串
	cout<<"json\n"<<json<<endl;
	Node&b=Serializable::loads<Node>(json);                                         //通过json格式的字符串进行反序列化
	cout<<endl<<a.get_config().serialized_to_string(true);
	cout<<endl<<b.get_config().serialized_to_string(true);                          //打印结果
}

程序运行结果:

(7).派生类的序列化与反序列化

#include"lang/serializable.h"
#include"iostream"
using namespace std;
struct Father
{
	int x=1;
	std::vector<int>y{1,2,3,4};
	virtual Config get_config()const
	{
		Config config=Serializable::get_config(this);
		config.update({
			{"x",x},
			{"y",y}
		});
		return config;
	}
	virtual ~Father(){}
};
struct Son:public Father
{
	float z[4]={5,6,7,8};
	Config get_config()const
	{
		Config config=Serializable::Inherit<Father>::get_config(this);
		config.update({
			{"z",z}
		});
		return config;
	}
};
struct GrandSon:public Son
{
	std::tuple<int,std::string>t={2021,"shijunfeng00"};
	Config get_config()const
	{
		Config config=Serializable::Inherit<Son>::get_config(this);
		config.update({
			{"t",t}
		});
		return config;
	}
};
int main()
{
	Serializable::Regist<Father,Son,GrandSon>();
	Father*son=new GrandSon();
	son->x=233;
	std::string json=Serializable::dumps(*son);
	cout<<"json string:\n"<<json<<endl;
	GrandSon*gs=(GrandSon*)Serializable::loads(json);
	cout<<gs->get_config().serialized_to_string(true)<<endl;
	cout<<gs->x<<endl;
	for(auto&it:gs->y)
		cout<<it<<",";
	cout<<endl;
	for(auto&it:gs->z)
		cout<<it<<",";
	cout<<endl;
	cout<<get<0>(gs->t)<<" "<<get<1>(gs->t);
}

程序运行结果:

六、反射的代码示例

反射相对于序列化/反序列化会多一些细节出来,如果只需要反射,可以只用#include"lang/reflectable.h"

以及对应的Reflectable::Regist()和Config config=Reflectable::get_config(this)

这样的话就只有反射没有序列化/反序列化的功能

代码示例(1):打印属性名称和方法名称

#include"lang/reflectable.h"
#include<iostream>
#include<vector>
using namespace std;
struct Node
{
    int x=1;
    float y=5;
    std::string z="sjf";
    int add(int x,int y)
    {
        return x+y;
    }
    std::string getName()
    {
        return z;
    }
    Config get_config()const
    {
        Config config=Reflectable::get_config(this);
        config.update({
            {"x",x},
            {"y",y},
            {"z",z},
            {"add",add},
            {"getName",getName}
        });
        return config;
    }
};
int main()
{
    Reflectable::Regist<Node>();
    Node a;
    cout<<"fields:\n";
    for(auto&it:Reflectable::get_field_names<Node>())
        cout<<"name:"<<it<<"    type:"<<Reflectable::get_field_type("Node",it)<<endl;  //打印属性名称
    cout<<"methods:\n";
    for(auto&it:Reflectable::get_method_names<Node>())                                     //打印方法名称
        cout<<"name:"<<it<<endl;
}

程序运行结果:

代码中可以使用Node*object=*(Node*)Reflectable::get_instance("Node")来得到一个Node对象,使用Reflectable::get_field来获得属性,对于private属性也能正常访问。

示例代码(2):完整功能示例

#include<iostream>
#include"lang/reflectable.h"
#include<vector>
using namespace std;
struct Node
{
    int x=1;
    float y=5;
    std::string z="sjf";
    int add(int x,int y)
    {
        return x+y;
    }
    std::string getName()
    {
        return z;
    }
    Config get_config()const
    {
        Config config=Reflectable::get_config(this);
        config.update({
            {"x",x},
            {"y",y},
            {"z",z},
            {"add",add},
            {"getName",getName}
        });
        return config;
    }
};
struct Point
{
    float x=0,y=0;
    Point add(float delta)
    {
        Point p(*this);
        p.x+=delta;
        p.y+=delta;
        return p;
    }
    Config get_config()const
    {
        Config config=Reflectable::get_config(this);
        config.update({
            {"x",x},
            {"y",y},
            {"add",add},
        });
        return config;
    }
};
int main()
{
    /*构建实例*/
    Reflectable::Regist<Node,Point>();
    void*a=Reflectable::get_instance("Node");                        //创建实例a
    Node b;                                                          //创建实例b
    Point c;
    /*属性值反射*/
    Reflectable::set_field(a,"Node","x",4);                          //不显式给出类型,由第三个参数来推断
    Reflectable::set_field<float>(a,"Node","y",5);
    Reflectable::set_field<string>(a,"Node","z","test");

    int field_x=Reflectable::get_field<int>(a,"Node","x");           //通过字符串名称得到值
    float field_y=Reflectable::get_field<float>(a,"Node","y");
    string field_z=Reflectable::get_field<string>(a,"Node","z");

    cout<<field_x<<" "<<(*(Node*)a).x<<endl;                         //正常访问成员变量
    cout<<field_y<<" "<<(*(Node*)a).y<<endl;
    cout<<field_z<<" "<<(*(Node*)a).z<<endl;

    Reflectable::set_field<int>(b,string("x"),10);
    Reflectable::set_field<float>(b,"y",15);
    Reflectable::set_field<string>(b,"z","cpp");

    field_x=Reflectable::get_field<int>(b,"x");
    field_y=Reflectable::get_field<float>(b,"y");
    field_z=Reflectable::get_field<std::string>(b,"z");

    cout<<field_x<<" "<<b.x<<endl;                                     //正常访问成员变量
    cout<<field_y<<" "<<b.y<<endl;
    cout<<field_z<<" "<<b.z<<endl;
    /*成员函数反射*/
    cout<<Reflectable::get_method<int>(b,"add",6,7)<<endl;
    cout<<Reflectable::get_method<string>(b,"getName")<<endl;
    Point d=Reflectable::get_method<Point>(c,"add",5.f);
    cout<<d.x<<" "<<d.y<<endl;
}

程序运行结果: