boost库开发笔记(一):boost::json使用与序列化
boost库
1998年,C++标准委员会创建了boost这个项目,初心是开发可复用的C++组件,为C++发展探索方向,boost早期严格遵循header-only原则,且代码评审规范,一度被称为最美的C++库之一,其次boost中的智能指针、regex、function/bind等一系列开发组件精华被C++标准库吸纳,成为C++事实上的重要参考库。由于boost是完全开源的,它可以前瞻性地完成一些C++开发中需要的组件,在项目上有重要应用成果。
因为项目需要,本系列会逐渐更新boost库的常用接口与原理,并持续更新。
从json方法开始。
boost::json
boost::json::value
基本数据类型构造与转换
object的value
和array
的成员都是以boost::json::value
的形式存储,因此解析时需要重新指定数据类型才能确保类型前后一致,类似QJson的toXXX接口,基本数据类型的boost::json::value
支持若干种解析转换方法:as_object、as_array、as_bool、as_double、as_int64、as_uint64和as_string
;对应这些数据,构造时只需要直接传递即可,json构造会自动完成这些基本数据类型到boost::json::value
的隐式转换。
is_xxx和if_xxx接口
boost::json::value
提供了类型判断is_xxx
接口,例如一个object存在多种类型的value,先判断再处理:
1
2
3
4
5
6for(const auto& pos : list){
if(pos.is_object()) //如为object对象
cout<< pos.as_object() <<endl; //转换
else if(pos.is_double()) //double对象
cout<< pos.as_double() <<endl;
}
if_xxx
接口的存在提供了类型判断和返回类型指针的双重功能,当满足对应数据类型才会返回对应的类型指针,利用这个指针可以具体对值进行校验:
1
2
3
4
5
6for(const auto& pos:jsonKv){
if(const uint64_t* item = pos.value().if_uint64()){
if(*item == 1998)
cout << pos.key() <<endl;
}
} uint64_t num = 1998
,而是在赋值中直接使用1998,那么是一律按照int类型存储。
get_xxx和emplace_xxx接口
二者均是不含类型判断的接口,get_xxx
会返回value对应类型的引用,如果类型不对应会导致断言失败,一般和类型判断结合使用:
1
2
3if(pos.value().is_double()){
double& item = pos.value().get_double();
}emplace_xxx
同样返回了对象类型的引用,而且会修改引用值为默认值,在不进行类型判断时尽管类型不对应,还是会完成修改:
1
double& item = pos.value().emplace_double(); //无论value原来是否double,都会被修改成double,且值为默认值0.0
boost::json::object
赋值方式:insert、emplace、直接赋值(operator[]/at())
object
对象支持若干种赋值方式,包括insert
、emplace
、直接赋值
:
如下实例Json: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{
"Company": "Committee",
"From": 1998,
"Name": "boost",
"Page": {
"Developers": "C++ Standard Committee",
"Download": "Download Link",
"Home": "Home Page"
},
"Version": [
1.0,
2.0,
3.0,
{"isTest": true}
]
}
通过以下方法完成json构造,大体上和QJson接口差不多: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//构造
boost::json::object jsonKv;
jsonKv["Company"] = "Committee";
jsonKv.emplace("From", 1998);
jsonKv.insert(std::make_pair("Name","boost"));
boost::json::object jsonPage;
jsonPage["Developers"] = "C++ Standard Committee";
jsonPage["Download"] = "Download Link";
jsonPage["Home"] = "Home Page";
jsonKv["Page"] = jsonPage;
jsonKv["Version"] = {
1.0, 2.0, 3.0, boost::json::object({{"isTest", true}})
};
这里最推荐使用的是直接赋值,因为它足够简洁,而且boost::json::object
的insert
对象是一种初始化列表对象,例如pair
和后续介绍的boost::json::value
,不能像QJsonObject一样直接插入键值,所以这样的代码在boost中是错误的:
1
jsonKv.insert("From", 1998);
如果你习惯这个接口,要么使用emplace
,要么写成:
1
2
3jsonKv.insert({{"From",1998}});
//或:
jsonKv.insert(std::make_pair("From",1998));1
std::pair<boost::json::string_view, boost::json::value>(..., ...);
注意一个细节差异:使用直接赋值时,同键插入时,json会保留最后一次插入的值;而如果json存在相同的键使用emplace
或insert
进行插入时,插入操作会失败,json只会保留原值。
std::initializer_list构造
可直接列表构造object
对象: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16boost::json::object jsonKv1{
{"Company","Committee"},
{"From", 1998},
{"Name", "boost"},
{"Page", {
{"Developers", "C++ Standard Committee"},
{"Download", "Download Link"},
{"Home", "Home Page"}
}},
{"Version",{
1.0,
2.0,
3.0,
{{"isTest", true}}}
}
};
遍历和删除
既支持迭代器,也支持const auto& pos : object
的方法:
1
2
3
4
5
6
7
8
9for(const auto& pos: jsonKv){
cout<< pos.key() <<pos.value()<<endl;
}
for(auto pos = jsonKv.begin(); pos!=jsonKv.end();){
if(pos->value().as_int64() == 1998)
pos = jsonKv.erase(pos);
else
pos++;
}erase
支持迭代器删除,也支持对键搜索删除:
1
jsonKv.erase("Company");
count/contain/size
接口同理,boost::json::object
内部实现类似std::unordered_map
,也支持reserve/capacity
等接口;
boost::json::array
array可以直接列表构造,也可以使用push;array还提供了emplace
接口表示在某个迭代器pos前插入某个value,但是注意其和erase
一样存在迭代器失效的问题,所以不能边迭代边插入,而是记录插入位置(当存在多个位置时,使用迭代器数组记录),然后后续再插入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25boost::json::array list{
1.0,
2.0,
3.0,
boost::json::object({{"isTest",true}})
};
list.push_back(4.0);
boost::json::array::iterator rec;
for(auto pos = list.begin(); pos!=list.end(); pos++){
if(double* item = pos->if_double()){
if(*item == 3.0){
rec = pos;
}
}
}
list.emplace(rec, 5.0); //在pos(3.0)前面插入5.0
for(const auto& pos:list){
if(pos.is_object())
cout<< pos.as_object() <<endl;
else if(pos.is_double())
cout<< pos.as_double() <<endl;
}
对象序列化与反序列化
应该说,boost库的对象序列化和反序列化思想和Qt是高度一致的,但是自定义规则方面其比Qt更加方便。
序列化的目标是将抽象的数据,例如int、double乃至结构体、STL数据封装成某些数据类型,因为我们要对这些数据进行传输,必须使通信双方都认识这些数据的存储;从底层来说,常用两种表示,其一是文本表示,即字符串;其二是二进制编码表示,如protobuf做的事情,这里涉及json我们仅讨论前者。
boost::json::value的序列化
这里对应的就是QJsonValue的序列化,将Json转换成字符串,或者将字符串转换到Json,分别对应parse
和serialize
接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using namespace std;
/*
{
"Company": "Committee",
"From": 1998,
"Name": "boost",
"Page": {
"Developers": "C++ Standard Committee",
"Download": "Download Link",
"Home": "Home Page"
},
"Version": [
1.0,
2.0,
3.0,
{"isTest": true}
]
}
*/
int main(){
uint64_t num = 1998;
boost::json::object jsonKv{
{"Company","Committee"},
{"From", num},
{"Name", "boost"},
{"Page", {
{"Developers", "C++ Standard Committee"},
{"Download", "Download Link"},
{"Home", "Home Page"}
}},
{"Version",{
1.0,
2.0,
3.0,
{{"isTest", true}}}
}
};
//json序列化到string
std::string s = boost::json::serialize(jsonKv);
cout << s <<endl;
//string解析到json
auto rJsonKv = boost::json::parse(s).as_object();
cout << rJsonKv << endl;
cout<< "done" <<endl;
return 0;
}
类、结构体、STL复杂对象的序列化与反序列化
boost对于复杂对象的序列化比Qt功能更加强大,Qt对上述对象进行序列化,只能是在结构体内、类内将成员变量塞入同一个QJsonObject或者QJsonArray,对于STL,也是同理;
boost的序列化和反序列化分别依赖于text_oarchive
和text_iarchive
,这个万能结构类似QVariant,能够直接装载类、结构体或者STL等复杂类型,而且它可以绑定字符串流或者文件流,将复杂类型直接转到对应的字符流,达到序列化目的,同理也可以读取相应的流数据,恢复原来类型,达到反序列化效果。
也如同QVariant装载类/结构体要使用Q_DECLARE_METATYPE(class/struct/class*/struct*)
,使得元对象系统能够认识这个类或结构体或指针,boost中也需要一个操作来使得boost::serialization
能够完成对复杂类型的序列化和反序列化,即在函数中声明一个模板函数,其命名和参数都是固定的:
1
2
3
4
5
6
7
8template<typename Archive>
void serialize(Archive& ar, const unsigned int version){
ar& num;
ar& num1;
ar& vp;
ar& umap;
ar& test;
}1
friend class boost::serialization::access; //当serialize私有,必须加上友元声明
如果类中存在结构体,也要对这个结构体声明同样的模板函数以定义序列化和反序列化规则,其他具体序列化结构boost自动帮我们完成了,我们只需使用text_oarchive
和text_iarchive
进行载入和读出操作即可,这也是比Qt优越的地方,再者,其还支持对指针对象进行序列化。
1 |
|
自定义序列化和反序列化规则:save与load
序列化和反序列化支持自定义的规则,例如在序列化Person时,我们想修改序列化值为某种默认值,可以这样写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26BOOST_SERIALIZATION_SPLIT_FREE(Person) //声明要自定义规则的类型
namespace boost
{
namespace serialization{
template<class Archive>
static void save(Archive &ar, const Person& p, const unsigned int version){
ar& p.num;
ar& p.num1;
ar& p.vp;
ar& p.umap;
ar& p.test;
}
template<class Archive>
static void load(Archive &ar, Person& p, const unsigned int version){
ar& p.num;
ar& p.num1;
ar& p.vp;
ar& p.umap;
ar& p.test;
p.num = 1000; //只要反序列化Person,就设置默认值
p.num1 = 9.999;
p.test.error = -999;
}
} // namespace serialization
} // namespace boostsave
函数中,ar
作为一个输出归档器(如boost::archive::text_oarchive
),相当于ar << p
;在load
函数ar
则相当于一个输入归档器(boost::archive::text_iarchive
),相当于ar >> p
。
对于BOOST_SERIALIZATION_SPLIT_FREE(Person)
,其作用是声明一个模板函数,以确认该类型需要按照自定义规则进行序列化和反序列化,既可以通过宏,也可以直接通过该函数指定:
1
2
3
4template<class Archive>
void serialize(Archive &ar, const Person& p, const unsigned int version){
split_free(ar, p, version);
}
注意一个重要特点:自定义规则对简单数据类型是无效的,例如你写下了对double
的自定义规则,想反序列化时对数据进行处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16BOOST_SERIALIZATION_SPLIT_FREE(double) //这是一段无效代码
namespace boost
{
namespace serialization{
template<class Archive>
void save(Archive &ar, const double& floatNum, const unsigned int version){
ar& floatNum;
}
template<class Archive>
void load(Archive &ar, double& floatNum, const unsigned int version){
ar& floatNum;
floatNum += 1; //不会如愿
}
} // namespace serialization
} // namespace boostdouble
,而你同时为类外的一个double
设置了自定义规则,就会有问题。所以事实上,对于ar& T
,如果T属于primitive_type
(基本数据类型、原生指针、C风格数组等,它们的共同点是都是按位写入内存,随即按位读出),那么在oa << T
中,根本不会查找save和load函数,而是按照默认行为进行写入读出;
所以使用自定义规则的,往往是类、结构体、boost::json::value/object/array
等,还有cv::Mat
;
对于类和结构体,其不过是一堆封装的成员函数,我们一开始已经讨论了其自定义方法。对于value
,其序列化的终点、反序列化的起点常常是std::string
,所以可以这样写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
using namespace std;
class Person{
public:
Person() = default;
Person(int num, double num1, int vpNum, float testNum):num(num),num1(num1),vp(10,vpNum){
test.error = testNum;
umap.insert({1,2});
umap.insert({3,4});
umap.insert({5,6});
obj["test"] = "begin";
}
void printInfo(){
cout << " num = " << num
<< " num1 = " << num1
<< " vpNum = " << vp[2]
<< " umap[3]= "<< umap[3]
<< " test.float = " << test.error << endl;
for(const auto& pos : obj){
cout << pos.key() << " " << pos.value() <<endl;
}
}
private:
friend class boost::serialization::access; //当serialize私有,必须加上友元声明
//此函数声明了序列化和反序列化规则
template<typename Archive>
void serialize(Archive& ar, const unsigned int version){
ar& num;
ar& num1;
ar& vp;
ar& umap;
ar& test;
ar& obj;
}
int num = 0;
double num1 = 0;
vector<int> vp;
unordered_map<int, int> umap;
struct Test{
float error = 0;
template<typename Archive>
void serialize(Archive& ar, const unsigned int version){
ar& error;
}
};
Test test;
boost::json::object obj;
};
BOOST_SERIALIZATION_SPLIT_FREE(boost::json::object)
namespace boost
{
namespace serialization{
template<class Archive>
void save(Archive &ar, const boost::json::object& obj, const unsigned int version){
std::string str = boost::json::serialize(obj);
ar& str;
}
template<class Archive>
void load(Archive &ar, boost::json::object& obj, const unsigned int version){
std::string str;
ar& str;
obj = boost::json::parse(str).as_object();
obj["load"] = "Done";
}
} // namespace serialization
} // namespace boost
int main(){
//复杂类到string
std::ostringstream ss;
boost::archive::text_oarchive oa(ss);
Person p(9, 9.9, 10, 19.9);
oa << p;
std::string output = ss.str();
//string反序列化到类
std::istringstream is(output);
boost::archive::text_iarchive oi(is);
Person p1;
oi >> p1;
p1.printInfo();
cout<< "done" <<endl;
return 0;
}serialize
和parse
早已支持json::value
到std::string
的转换和反转换,定义规则只是满足一些特殊需求,或者使代码更统一;二是自定义规则会影响每一个归档器对象,无论该对象在类内或是类外被定义。
boost风格cv::Mat的序列化与反序列化
参考链接: