前言
C++ nlohmann/json 库是一个非常易用,高性能的 json 库。
CMake FetchContent方式集成
1
2
3
4
5
6
| include(FetchContent)
FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
FetchContent_MakeAvailable(json)
target_link_libraries(json_demo PRIVATE nlohmann_json::nlohmann_json)
|
快速使用
包含头文件以及声明命名空间别名:
1
2
| #include <nlohmann/json.hpp>
using json = nlohmann::json;
|
解析json
从文件读取解析
1
2
3
4
5
6
7
8
| // method 1
std::ifstream f("example.json");
json data = json::parse(f);
// method 2
std::ifstream i("file.json");
json j;
i >> j;
|
从字符串读取解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // Using (raw) string literals and json::parse
json ex1 = json::parse(R"(
{
"pi": 3.141,
"happy": true
}
)");
// Using user-defined (raw) string literals
using namespace nlohmann::literals;
json ex2 = R"(
{
"pi": 3.141,
"happy": true
}
)"_json;
// Using initializer lists
json ex3 = {
{"happy", true},
{"pi", 3.141},
};
|
遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // special iterator member functions for objects
for (json::iterator it = o.begin(); it != o.end(); ++it) {
std::cout << it.key() << " : " << it.value() << "\n";
}
// the same code as range for
for (auto& el : o.items()) {
std::cout << el.key() << " : " << el.value() << "\n";
}
// even easier with structured bindings (C++17)
for (auto& [key, value] : o.items()) {
std::cout << key << " : " << value << "\n";
}
|
查找 Key
1
2
3
4
5
| if (j.contains("key")) {
}
if (j.find("foo") != o.end()) {
}
|
读取 key
1
2
3
4
5
6
7
8
| auto value = j["key"];
auto value = j.at("key");
std::string value = j["key"].template get<std::string>();
// C++17
using namespace std::literals;
// 如果key不存在,则返回默认值0
int v_integer = j.value("integer"sv, 0);
|
删除 key
1
2
| // delete an entry
o.erase("foo");
|
注意:数组可能无法删除单个元素
写入 json 文件
1
2
| std::ofstream o("pretty.json");
o << std::setw(4) << j << std::endl;
|
序列化设置缩进
1
2
| // 按照四个空格缩进打印json
std::cout << j.dump(4) << std::endl;
|
任意类型转换
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
| using json = nlohmann::json;
namespace ns {
struct person {
std::string name;
std::string address;
int age;
};
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
// create a person
ns::person p {"Ned Flanders", "744 Evergreen Terrace", 60};
// conversion: person -> json
json j = p;
std::cout << j << std::endl;
// {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}
// conversion: json -> person
auto p2 = j.template get<ns::person>();
|
通过实现to_json()
和from_json()
完成 json 与结构体的映射关系。
[!warning]
- 使用
template get<your_type>()
时,必须保证是可以默认构造的。 from_json
中,使用 at()
来访问对象的值,而不是 operator[]
。如果某个键不存在, at
会抛出一个可以处理的异常,而 operator[]
则会表现出未定义的行为。
如果想要将 JSON 对象用作序列化或想要将成员变量名称用作该对象的键。可以使用宏简化操作(自动实现to_json()
和from_json()
):
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...)
:在类/结构体的命名空间内进行定义。NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...)
:在类/结构内部定义的标识符,用于创建代码。这个宏还可以访问私有成员。
命名空间中使用:
1
2
3
4
5
6
7
8
| namespace ns {
struct person {
std::string name;
std::string address;
int age;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age)
}
|
使用私有成员:
1
2
3
4
5
6
7
8
9
10
11
| namespace ns {
class address {
private:
std::string street;
int housenumber;
int postcode;
public:
NLOHMANN_DEFINE_TYPE_INTRUSIVE(address, street, housenumber, postcode)
};
}
|
宏相当于:
1
2
3
4
5
6
7
8
9
10
11
12
13
| void to_json(nlohmann::json& nlohmann_json_j, const person& nlohmann_json_t)
{
nlohmann_json_j["name"] = nlohmann_json_t.name;
nlohmann_json_j["address"] = nlohmann_json_t.address;
nlohmann_json_j["age"] = nlohmann_json_t.age;
}
void from_json(const nlohmann::json& nlohmann_json_j, person& nlohmann_json_t)
{
nlohmann_json_t.name = nlohmann_json_j.at("name");
nlohmann_json_t.address = nlohmann_json_j.at("address");
nlohmann_json_t.age = nlohmann_json_j.at("age");
}
|
注意:
使用宏时,如果 json 中缺少某个键,赋值给结构体时,则会抛出 out_of_range.403
异常。但是 json 中如果有多余的键,则会自动忽略。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // incomplete deserialization:
json j3 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson"})"_json;
try
{
auto p3 = j3.template get<ns::person>();
}
catch (const json::exception& e)
{
std::cout << "deserialization failed: " << e.what() << std::endl;
}
// output:
// deserialization failed: [json.exception.out_of_range.403] key 'age' not found
// redundant fields
json j2 = R"({"address": "742 Evergreen Terrace", "name": "Maggie Simpson", "age": 20, "sex": "man"})"_json;
ns::person p2 = j2.template get<ns::person>();
|
目前 nlohmann 最新 Release 版本为 3.11.3,还未支持 std::optional
,所以当遇到 json 为 null 时,需要额外处理,通过重新定义宏的方式,处理 null 的情形。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #ifdef NLOHMANN_JSON_FROM
#pragma push_macro("NLOHMANN_JSON_FROM")
#undef NLOHMANN_JSON_FROM
#endif
// ignore null in json
#define NLOHMANN_JSON_FROM(v1) \
{ \
if (nlohmann_json_j.at(#v1).is_null()) { \
nlohmann_json_t.v1 = {}; \
} else { \
nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); \
} \
}
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(MakeupItem, id, name, order_no, status, makeup_key, created_at, updated_at,
deleted_at, infos, colors)
#ifdef NLOHMANN_JSON_FROM
#pragma pop_macro("NLOHMANN_JSON_FROM")
#endif
|
总结
以上就是在使用 json 库时的常用场景。
https://github.com/nlohmann/json
https://github.com/nlohmann/json/tree/develop/docs/examples
https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/#examples