首先你可能需要了解一下json格式,它实际上还是比较简单的一种格式,理解起来还是很容易的,如果你对它不熟悉,可以参考这份教程快速学习一下:json 教程
1. 定义json数值类型
如果你想要创建一个如下这样形式的json对象:
{
"pi": 3.141,
"happy": true,
"name": "niels",
"nothing": null,
"answer": {
"everything": 42
},
"list": [1, 0, 2],
"object": {
"currency": "usd",
"value": 42.99
}
}
使用nlohmann库可以非常方便的完成。
json j; //首先创建一个空的json对象
j["pi"] = 3.141; //然后通过名称/值对的方式进行初始化,此时名称"pi"对应的数值就是3.141
j["happy"] = true;//将名称"happy"赋值为true
j["name"] = "niels";//将字符串"niels"存储到"name"
j["nothing"] = nullptr;//"nothing"对应的是空指针
j["answer"]["everything"] = 42;//对对象中的对象进行初始化
j["list"] = {
1, 0, 2 };//使用列表初始化的方法对"list"数组初始化
j["object"] = {
{
"currency", "usd"}, {
"value", 42.99} };//对对象进行初始化
注意: 在进行如上方式构造json对象时,你并不需要告诉编译器你要使用哪种类型,nlohmann会自动进行隐式转换。
如果你想显式定义或者表达一些情况,可以考虑如下这样的方法:
json empty_array_explicit = json::array();//初始化一个json格式的空数组
json empty_object_implicit = json({
});//隐式定义一个空对象
json empty_object_explicit = json::object();//显式定义一个空对象
json array_not_object = json::array({
{
"currency", "usd"}, {
"value", 42.99} });//显式定义并初始化一个json数组
2.从stl容器转换到json
nlohmann库支持从stl的任意序列容器初始化获得json对象(std::array, std::vector, std::deque, std::forward_list, std::list),它们的值可以被用来构造json的值。
std::set, std::multiset, std::unordered_set, std::unordered_multiset关联容器也具有同样的用法,并且也会保存其内在的顺序。
另外像std::map, std::multimap, std::unordered_map, std::unordered_multimap,nlohmann也是支持的,但是需要注意的是其中的key被构造为std::string
保存。
std::vector c_vector {
1, 2, 3, 4};
json j_vec(c_vector);
// [1, 2, 3, 4]
std::deque c_deque {
1.2, 2.3, 3.4, 5.6};
json j_deque(c_deque);
// [1.2, 2.3, 3.4, 5.6]
std::list c_list {
true, true, false, true};
json j_list(c_list);
// [true, true, false, true]
std::forward_list c_flist {
12345678909876, 23456789098765, 34567890987654, 45678909876543};
json j_flist(c_flist);
// [12345678909876, 23456789098765, 34567890987654, 45678909876543]
std::array c_array {
{
1, 2, 3, 4}};
json j_array(c_array);
// [1, 2, 3, 4]
std::set c_set {
"one", "two", "three", "four", "one"};
json j_set(c_set); // only one entry for "one" is used
// ["four", "one", "three", "two"]
std::unordered_set c_uset {
"one", "two", "three", "four", "one"};
json j_uset(c_uset); // only one entry for "one" is used
// maybe ["two", "three", "four", "one"]
std::multiset c_mset {
"one", "two", "one", "four"};
json j_mset(c_mset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]
std::unordered_multiset c_umset {
"one", "two", "one", "four"};
json j_umset(c_umset); // both entries for "one" are used
// maybe ["one", "two", "one", "four"]
std::map c_map {
{
"one", 1}, {
"two", 2}, {
"three", 3} };
json j_map(c_map);
// {"one": 1, "three": 3, "two": 2 }
std::unordered_map c_umap {
{
"one", 1.2}, {
"two", 2.3}, {
"three", 3.4} };
json j_umap(c_umap);
// {"one": 1.2, "two": 2.3, "three": 3.4}
std::multimap c_mmap {
{
"one", true}, {
"two", true}, {
"three", false}, {
"three", true} };
json j_mmap(c_mmap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}
std::unordered_multimap c_ummap {
{
"one", true}, {
"two", true}, {
"three", false}, {
"three", true} };
json j_ummap(c_ummap); // only one entry for key "three" is used
// maybe {"one": true, "two": true, "three": true}
当你使用这些通过stl容器构造的json对象时,你只需要安排stl容器那样的方式去使用它。
3.string序列化和反序列化
- 反序列化:从字节序列恢复json对象。
json j = "{ \"happy\": true, \"pi\": 3.141 }"_json;
auto j2 = r"({"happy": true,"pi": 3.141})"_json;
- 序列化:从json对象转化为字节序列。
std::string s = j.dump(); // {"happy":true,"pi":3.141}
std::cout << j.dump(4) << std::endl;//按照如下形式输出
// {
// "happy": true,
// "pi": 3.141
// }
dump()返回json对象中存储的原始string值。
4.stream的序列化和反序列化
标准输出(std::cout)和标准输入(std::cin)
json j;
std::cin >> j;//从标准输入中反序列化json对象
std::cout << j;//将json对象序列化到标准输出中
将json对象序列化到本地文件,或者从存储在本地的文件中反序列化出json对象,是非常常用的一个操作。nlohmann对于这个操作也很简单。
//读取一个json文件,nlohmann会自动解析其中数据
std::ifstream i("file.json");
json j;
i >> j;
//以易于查看的方式将json对象写入到本地文件
std::ofstream o("pretty.json");
o << std::setw(4) << j << std::endl;
5. 任意类型转换
我这里总结一下对于任意类型的转换方法。
对于某一种任意数据类型,可以使用如下方式转换:
namespace ns {
//首先定义一个结构体
struct person {
std::string name;
std::string address;
int age;
};
}
ns::person p = {
"ned flanders", "744 evergreen terrace", 60};//定义初始化p
//从结构体转换到json对象
json j;
j["name"] = p.name;
j["address"] = p.address;
j["age"] = p.age;
//从json对象转换到结构体
ns::person p {
j["name"].get(),
j["address"].get(),
j["age"].get()
};
但是这样的方式在经常需要转换的场景下就不方便了,nlohmann提供了更为方便的方式:
using nlohmann::json;
namespace ns {
void to_json(json& j, const person& p) {
j = json{
{
"name", p.name}, {
"address", p.address}, {
"age", p.age}};
}
void from_json(const json& j, person& p) {
j.at("name").get_to(p.name);
j.at("address").get_to(p.address);
j.at("age").get_to(p.age);
}
} // namespace ns
ns::person p {
"ned flanders", "744 evergreen terrace", 60};
json j = p;
std::cout << j << std::endl;
// {"address":"744 evergreen terrace","age":60,"name":"ned flanders"}
// conversion: json -> person
auto p2 = j.get();
// that's it
assert(p == p2);
只需要在person结构体所在的命名空间下,定义函数to_json()
和from_json()
就可以轻松的完成任意类型到json对象的转换,以及json转换为任意对象。
注意:
- 1.函数
to_json()
和from_json()
和你定义的数据类型在同一个命名空间中; - 2.当你要在另外的文件中使用这两个函数时,要正确的包含它所在的头文件;
- 3.在
from_json
中要使用at()
,因为当你读取不存在的名称时,它会抛出错误。
当可以将任意类型数据方便的转换到json,就可以将这个json方便的序列化到本地保存,也可以从本地反序列化到内存中,比自己去写每一个字节的操作方便多了。
6.建议使用显式类型转换
当从json对象中获取数据时,强烈建议使用显式类型转换。例如:
std::string s1 = "hello, world!";
json js = s1;
auto s2 = js.get();
//不建议
std::string s3 = js;
std::string s4;
s4 = js;
// booleans
bool b1 = true;
json jb = b1;
auto b2 = jb.get();
//不建议
bool b3 = jb;
bool b4;
b4 = jb;
// numbers
int i = 42;
json jn = i;
auto f = jn.get();
//不建议
double f2 = jb;
double f3;
f3 = jb;
// etc.
7.转换json到二进制格式
尽管json格式非常常用,但是它的缺点也很明显,它并不是一种紧凑的格式,不适合通过网络传输,或者写到本地,常常需要将json对象就行二进制转换,nlohmann库支持多种二进制格式,包括bson,cbor,messagepack和ubjson.
// create a json value
json j = r"({"compact": true, "schema": 0})"_json;
// serialize to bson
std::vector v_bson = json::to_bson(j);
// 0x1b, 0x00, 0x00, 0x00, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x00, 0x01, 0x10, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
// roundtrip
json j_from_bson = json::from_bson(v_bson);
// serialize to cbor
std::vector v_cbor = json::to_cbor(j);
// 0xa2, 0x67, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xf5, 0x66, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x00
// roundtrip
json j_from_cbor = json::from_cbor(v_cbor);
// serialize to messagepack
std::vector v_msgpack = json::to_msgpack(j);
// 0x82, 0xa7, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xc3, 0xa6, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x00
// roundtrip
json j_from_msgpack = json::from_msgpack(v_msgpack);
// serialize to ubjson
std::vector v_ubjson = json::to_ubjson(j);
// 0x7b, 0x69, 0x07, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x54, 0x69, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x69, 0x00, 0x7d
// roundtrip
json j_from_ubjson = json::from_ubjson(v_ubjson);
如果需要写到本地,可以使用如下方式:
std::ofstream ofs(path, std::ios::out | std::ios::binary);
const auto msgpack = nlohmann::json::to_msgpack(json);
ofs.write(reinterpret_cast(msgpack.data()), msgpack.size() * sizeof(uint8_t));
ofs.close();
8.例子
该例子介绍了怎么保存对象中的成员变量到本地,并且从本地读取二进制反序列化对象。
#include
#include
#include
#include
温馨提示:虽然将数据转换为json对象,然后进行保存和序列化会很方便,但是当数据类巨大时,达到百万或者千万的级别时,再使用这样的方式,在构造json对象时会消耗很多时间,我试过一个大概300m的数据,转化为json对象时大概需要几十秒。而300m兆的数据写到本地时只需要0.1秒。所以此时最好还是直接对原始的数据进行操作写入本地,就别再转化到json对象中了,虽然算法会麻烦一些,但是获得的速度是非常快的。
参考资料:
- nlohmann/json