Json是一种轻量级的数据交换格式(也叫数据序列化方式)。Json采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 Json 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
数据序列化格式还有:xml、protobuf,其中protobuf存储数据更为高效,在企业级项目中更常见。——RPC项目用的就是protobuf
本项目中用的Json第三方库是Json for Modern C++,由德国大牛nlohmann编写的在C++下使用的Json库。该库只有一个json.hpp头文件,使用C++11编写,使得使用json就像使用STL容器一样,且STL容器和json之间可以相互转换。
序列化是将数据结构或是对象转换为二进制串(字节序列)的过程,也就是将具有一定结构的对象转换为可以存储或传输的形式。在序列化期间,对象将其当前状态写入到临时或持久性存储区(如硬盘)。这样我们就可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
反序列化就是上述的逆过程。
比如我们把json格式的对象转换成字符串,再转换成char*,这样就能在网络上传输,或者写进硬盘——由结构化的数据转成地址值
反过来我们把从网络上接收到的字符串对象转成json对象,就是重新构建出json对象结构,这就是反序列化
所以对象序列化的目的:
1、把对象的字节序列永久保存在硬盘上(以某种储存方式使自定义对象持久化);
2、在网络上传送对象的二进制序列(将对象从一个地方传递到另一个地方);
3、使程序更具维护性。
其实就是两个函数:
序列化--------js.dump()//不用传参,json对象js访问成员函数
反序列化-----json::parse(字符串对象)
下列测试代码来自施磊老师课程笔记
#include "json.hpp"
using json = nlohmann::json;
#include
using namespace std;
//json序列化演示
void func1(){
json js;
//就像map容器一样,key - value
js["msg_num"] = 2;
js["from"] = "zhang san";
js["to"] = "li si";
js["mas"] = "hello, it's my first cpp program";
cout<< js <<endl;
}
int main(){
func1();
return 0;
}
神奇,可以直接输出json对象js————json肯定对输出运算符做了重载,存数据的时候又能像map容器一样。
输出结果:
{"from":"zhang san","mas":"hello, it's my first cpp program","msg_type":2,"to":"li si"}
可以看到,并不是msg_type第一个插进去就第一个输出/第一个位置存,它是按照key的首字母顺序来存放和输出的,就像有序哈希表map一样
json 转化为 字符串:
json要通过网络发送肯定还是要转成字符串类型,而string在网络上传播是char*形式
string Str = json.dump();//dump函数就可以将json的序列化数据转成string形式,dump本意有转储,导出的意思
//string也封装了将string转换为char*的方法
char* a = Str.c_str();
cout<< a <<endl;
变成字符串其实输出的还是之前那样的结果,相当于在外面加了“”
符号
json的键对应的值value除了上面的int ,string类型,还可以是数组类型,甚至是json类型,而且键本身也可以是二维数组类型,如下:
void func2(){
json js;
js["id"] = {1, 2, 3, 4, 5};
js["name"] = "zhang san";
//添加对象,key可以是二维数组
js["msg"]["liu shuo"] = "hello A";
js["msg"]["zhang san"] = "hello B";//js[键1][键2]
//下面等同于上面两行,两种形式输出都一样
js["msg"] = {{"liu shuo", "hello A"}, {"zhang san", "hello B"}};//用两个json对象给js的msg赋值
cout<<js<<endl;
}
输出:
{"id":[1,2,3,4,5],"msg":{"liu shuo":"hello A","zhang san":"hello B"},"name":"zhang san"}
可以看到msg对应的值是一个json,两个json赋值给msg后变成了一个json,而且,js["msg"]["liu shuo"]
的意义就是先访问js的键msg,再访问msg中的键liu shuo,还有就是,liu和zhang的顺序也被改了,因为两者在msg这个json里面就是键,是有顺序的
json甚至能将容器序列化,其实就是value还可以是容器类型
void func3(){
json js;
//序列化vector容器
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(5);
js["list"] = vec;
//序列化map容器
map<int, string> m;
m.insert({1, "黄山"});
m.insert({2, "华山"});
m.insert({3, "泰山"});
js["path"] = m;
cout<<js<<endl;
}
输出:
{"list":[1,2,5],"path":[[1,"黄山"],[2,"华山"],[3,"泰山"]]}
map这个键值对容器转成json后,变成二维数组了
假设我们收到字符串形式的json,我们把它转成json格式,用json::parse()
函数,parse是语法分析,解析。
其实就是,对方服务器有json数据,转成字符串,这样方便发给我们,我们再把它重塑为json
下面测试123是对应上面测试123的反序列化
string func1(){
json js;
//就像map容器一样,key - value
js["msg_type"] = 2;
js["from"] = "zhang san";
js["to"] = "li si";
js["msg"] = "hello, it's my first cpp program";
string Str = js.dump();
return Str;
}
int main(){
string s = func1();
json js = json::parse(s);
cout<<js["msg_type"]<<endl;
cout<<js["from"]<<endl;
cout<<js["msg"]<<endl;
return 0;
}
输出:
2
"zhang san"
"hello, it's my first cpp program"
string func2(){
json js;
js["id"] = {1, 2, 3, 4, 5};
js["name"] = "zhang san";
js["msg"]["liu shuo"] = "hello A";
js["msg"]["zhang san"] = "hello B";
return js.dump();
}
int main(){
json js = json::parse(func2());
cout<<js["id"]<<endl;
cout<<js["msg"]<<endl;
cout<<js["msg"]["zhang san"]<<endl;
return 0;
}
输出:
[1,2,3,4,5]
{"liu shuo":"hello A","zhang san":"hello B"}
"hello B"
下面测试3更能体现反序列化的意义!
string func3(){
json js;
//序列化vector容器
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(5);
js["list"] = vec;
//序列化map容器
map<int, string> m;
m.insert({1, "黄山"});
m.insert({2, "华山"});
m.insert({3, "泰山"});
js["path"] = m;
return js.dump();
}
int main(){
json js = json::parse(func3());
vector<int> nums = js["list"];//因为js里面的list的值是vector形式数据
map<int, string>mp = js["path"];
for(auto& a : nums){
cout<<a<<endl;
}
for(int i = 0; i < mp.size(); ++i){
cout<<mp[i]<<endl;
}
return 0;
}
结果:
1
2
5
//这里输出了一个空字符串,map[0]对应的value(string类型)
黄山
华山
泰山
上述代码中,用下标去给map做输出,而map的[]
里应该是key,所以i = 0时,因为map没有对应元素,所以被新增到容器中,此时容器大小变为4,所以能访问下标[3],map[0]其对应的初始值为空字符串
所以用迭代器访问map更好,这样不会新增元素,而且不会越界。
for(auto& p : mp){
cout<<p.first<<" "<<p.second<<endl;
}