文章目录
- 定义
- 利用 Json 实现序列化反序列化
- Json 的认识
- Jsoncpp 库的下载与认识
- 实现序列化
- 实现反序列化
在网络编程中,直接使用 结构体 进行数据传输会出错,因为本质上socket无法传输结构体,我们只有将结构体装换为字节数组,或者是字符串格式来传输,然后对端主机收到了数据,再将其转化为结构体,这就是序列化和反序列化的过程!
定义
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
序列化主要有两个用途:
- 把对象的字节序列永久保存到硬盘上,通常存放在一个文件中(序列化对象)
- 在网络上传送对象的字节序列(网络传输对象)
实际上就是将数据持久化,防止一直存储在内存当中,消耗内存资源。而且序列化后也能更好的便于网络运输何传播。
如下,在网上找到的一张图片,可以很好地描述序列化和反序列化,及其作用(以 java 对象为例)。
利用 Json 实现序列化反序列化
Json 的认识
json 是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。
例如:小明同学的学生信息:
char name = "小明";
int age = 18;
float score[3] = {88.5, 99, 58};
则json这种数据交换格式是将这多种数据对象组织成为一个字符串:
[
{
"姓名" : "小明",
"年龄" : 18,
"成绩" : [88.5, 99, 58]
},
{
"姓名" : "小黑",
"年龄" : 18,
"成绩" : [88.5, 99, 58]
}
]
json 数据类型:对象,数组,字符串,数字。
- 对象:使用花括号 {} 括起来的表示一个对象。
- 数组:使用中括号 [] 括起来的表示一个数组。
- 字符串:使用常规双引号 “” 括起来的表示一个字符串
- 数字:包括整形和浮点型,直接使用。
Jsoncpp 库的下载与认识
sudo yum install epel-release
sudo yum install jsoncpp-devel
依次执行上面两个指令,然后在 /usr/include/jsoncpp/json/ 目录下查看是否有相关文件,如下图,有则下载成功。
jsoncpp 库用于实现 json 格式的序列化和反序列化,完成将多个数据对象组织成为 json 格式字符串,以及将 json格式字符串解析得到多个数据对象的功能。
这其中主要借助三个类以及其对应的少量成员函数完成:
在这里过多介绍下面接口肯略微抽象,要在下面的使用过程中去理解其用法。
//Json数据对象类
class Json::Value {
Value &operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取数据都可以通过
Value& operator[](const std::string& key);//简单的方式完成 val["姓名"] = "小明";
Value& operator[](const char* key);
Value removeMember(const char* key);//移除元素
const Value& operator[](ArrayIndex index) const; //val["成绩"][0]
Value& append(const Value& value);//添加数组元素val["成绩"].append(88);
ArrayIndex size() const;//获取数组元素个数 val["成绩"].size();
std::string asString() const;//转string string name = val["name"].asString();
const char* asCString() const;//转char* char *name = val["name"].asCString();
Int asInt() const;//转int int age = val["age"].asInt();
float asFloat() const;//转float
bool asBool() const;//转 bool
};
//json序列化类,低版本用这个更简单
class JSON_API Writer {
virtual std::string write(const Value& root) = 0;
}
class JSON_API FastWriter : public Writer {
virtual std::string write(const Value& root);
}
class JSON_API StyledWriter : public Writer {
virtual std::string write(const Value& root);
}
//json序列化类,高版本推荐,如果用低版本的接口可能会有警告
class JSON_API StreamWriter {
virtual int write(Value const& root, std::ostream* sout) = 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
virtual StreamWriter* newStreamWriter() const;
}
//json反序列化类,低版本用起来更简单
class JSON_API Reader {
bool parse(const std::string& document, Value& root, bool collectComments = true);
}
//json反序列化类,高版本更推荐
class JSON_API CharReader {
virtual bool parse(char const* beginDoc, char const* endDoc,
Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory {
virtual CharReader* newCharReader() const;
}
实现序列化
#include<iostream>
#include<jsoncpp/json/json.h>
#include<memory>
#include<sstream>
using namespace std;
int main()
{
const char *name = "小明";
int age = 18;
float score[] = {88.5, 98, 58};
Json::Value val;
// 在val对象中写入数据
val["姓名"] = name;
val["年龄"] = age;
val["成绩"].append(score[0]); // 因为 score 是数组,所以存入Json::Value 对象要用 append
val["成绩"].append(score[1]);
val["成绩"].append(score[2]);
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::ostringstream os;
sw->write(val, &os); // 将 val对象序列化,并放到 os 中
std::string str = os.str();
std::cout << str <<std::endl; // 打印序列化之后的内容
return 0;
}
如上代码,有 val 对象,里面存储了一些数据(姓名、年龄、成绩),然后将其序列化,打印序列化之后的内容,结果如下。
序列化的过程我们无需关心,因为这是 StreamWriter 封装好的,我们只需要调用其 write 接口,传入参数即可。
此时,这些序列化之后的内容,就可以在网络中传输,也就是说,如果要进行网络通信传输这些数据,就可以调用 write/sendto/send 等等接口,将上面代码中的 str 进行传输!而不是直接传输 val 对象!
实现反序列化
#include<iostream>
#include<jsoncpp/json/json.h>
#include<memory>
#include<sstream>
using namespace std;
int main()
{
std::string str = R"({"姓名":"小明", "年龄":18, "成绩":[76.5, 55, 88]})";
Json::Value root;
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err;
cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err); // 反序列化
std::cout << root["姓名"].asString() << std::endl;
std::cout << root["年龄"].asInt() << std::endl;
int sz = root["成绩"].size();
for (int i = 0; i < sz; i++) {
std::cout << root["成绩"][i].asFloat() << std::endl;
}
// 第二种遍历方式
for (auto it = root["成绩"].begin(); it != root["成绩"].end(); it++){
std::cout << it->asFloat() << std::endl;
}
return 0;
}
如上,现在有一段序列化之后的数据 str,里面的内容是完全按照 json 的格式来组织的。现在要完成反序列化,将其数据放入 root 对象中。
本质上只要调用 CharReader 的 parse 接口,依次传参即可,然后 root 中就会被放入数据。
当然,实现序列化和反序列化不是只有用 jsoncpp 库这一个办法,还有其他办法例如:protbuf 等等。