C++ 实现 JSON 序列化和反序列化

大纲

前言

本文将介绍如何使用 nlohmann/json 库实现 C++ 的 JSON 序列化和反序列化。在网络编程中,常用的数据序列化格式包括 XML、JSON 和 Protobuf。在企业级项目中,Protobuf 被广泛采用,因其具备高效的压缩能力和低带宽占用。相同的数据内容,Protobuf 的体积约为 JSON 的 1/10、XML 的 1/20,极大提升了传输效率。尽管使用上相较 JSON 略显复杂,但在对性能和带宽要求较高的场景中,Protobuf 是更优的选择。

版本说明

本文使用各软件的版本如下表所示:

软件版本说明
C++ 标准11
nlohmann/json3.12.0Json 库,基于 C++ 开发,用于 Json 序列化和反序列化
G++(GCC)12.2.0建议使用 5.57.5 版本的 G++(GCC) 编译器

主流 JSON 库

RapidJSON

RapidJSON 是由腾讯开源的一个 C++ JSON 解析与生成库,以极速性能为核心特点。它是一个头文件库(Header-Only),无需编译、依赖少,适合对性能要求极高的项目。

  • 主要特点:

    • 超高速:极致优化的 JSON 解析与生成性能,是 C++ 中最快的 JSON 库之一。
    • 零依赖、跨平台:纯 C++ 实现,无需依赖 STL(可选开启),适用于嵌入式系统。
    • 严格遵循 JSON 标准:完全符合 RFC 7159 / ECMA-404
    • 内存控制精细:支持自定义内存分配器,可用于嵌入式、游戏等内存敏感场景。
    • 支持 SAX / DOM 双模式:可用于不同性能与易用性需求的场景。
  • 安装方式:

    • 只需将头文件直接复制到项目的 include 目录中,然后引入即可:
      1
      2
      3
      #include <rapidjson/document.h>
      #include <rapidjson/writer.h>
      #include <rapidjson/stringbuffer.h>
  • 适用场景:

    • 性能要求高的场景:如游戏引擎、嵌入式设备、实时系统、交易系统等。
    • 不需要 STL(可选开启)的轻量应用。
    • 需要手动管理 JSON 内存、定制行为的专业用途。
  • 注意事项:

    • 相比 nlohmann/json,RapidJSON 更偏向底层和性能,使用复杂度更高。
    • 如果追求易用性和语法糖,推荐使用 nlohmann/json
    • 如果追求极致性能和精细内存控制,RapidJSON 是首选。

nlohmann/json

nlohmann/json 是一个非常流行的 C++ 开源 JSON 库,以其简洁易用、接口友好和现代 C++ 特性支持而著称。它的设计目标是让 JSON 的使用尽可能像标准容器(如 std::mapstd::vector)一样自然,几乎可以零学习成本上手。

  • 主要特点:

    • 现代 C++ 风格:基于 C++11/14/17 编写,支持智能指针、范围 for 循环、初始化列表等特性。
    • 类似 STL 的接口:使用方式类似 std::mapstd::vector
    • 单头文件:只需包含一个头文件 json.hpp,无须额外依赖。
    • 支持多种数据类型:支持 intfloatboolstringvectormap、自定义结构体等。
    • 强大的序列化与反序列化:可以方便地将 JSON 转换为 C++ 对象,或将 C++ 对象转为 JSON。
    • 格式美观的输出:支持格式化打印 JSON 数据。
    • 严谨的测试:所有类都经过严格的单元测试,覆盖了 100% 的代码,包括所有特殊的行为。此外,还使用了 Valgrind 来检查是否有内存泄漏。
  • 安装方式:

    • 最简单的方式是只包含一个头文件:
      1
      2
      #include <nlohmann/json.hpp>
      using json = nlohmann::json;
    • 还可以使用包管理工具如:
      • vcpkg:vcpkg install nlohmann-json
      • Conan:conan install nlohmann_json/3.11.2@
  • 使用建议

    • 对于配置文件、网络通信、日志记录、REST API 开发等场景都非常适用。
    • 特别注意:虽然功能强大,但在性能要求极高的场合(如高频交易)可能不如手写解析或使用高性能库(如 RapidJSON)。

使用 JSON 库

JSON 库安装

  • (1) 在 GitHub 仓库 下载所需版本的 json.hpp 头文件,然后将其拷贝到 C++ 项目的 include 目录中,如下图所示:

  • (2) 最简单的使用方式是只包含 json.hpp 头文件

    1
    2
    #include <nlohmann/json.hpp>
    using json = nlohmann::json;

JSON 序列化

JSON 序列化就是将需要打包的数据或者对象,直接转换成 JSON 字符串。

案例一

  • 普通数据的序列化
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
#include <iostream>
#include "json.hpp"

using namespace std;

// 类型重定义
using json = nlohmann::json;

int main() {
// JSON 对象
json j1;

// 添加数组
j1["id"] = {1, 2, 3, 4, 5};

// 添加 key-value
j1["name"] = "zhang san";

// 添加对象
j1["msg"]["zhang san"] = "hello go";
j1["msg"]["liu suo"] = "hello python";

// 上面这两行代码等同于下面这行一次性添加对象
// obj["msg"] = {{"zhang san", "hello go"}, {"liu suo", "hello python"}};

// 输出序列化后的JSON字符串
cout << j1 << endl;

return 0;
}

程序运行输出的结果:

1
{"id":[1,2,3,4,5],"msg":{"liu suo":"hello python","zhang san":"hello go"},"name":"zhang san"}

案例二

  • STL 容器的序列化
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
#include <iostream>
#include <vector>
#include "json.hpp"

using namespace std;

// 类型重定义
using json = nlohmann::json;

int main() {
// JSON对象
json js;

// 定义一个Vector容器
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);

// 直接序列化一个Vector容器
js["list"] = vec;

// 定义一个Map容器
map<int, string> m;
m.insert({1, "Go"});
m.insert({2, "Rust"});
m.insert({3, "Python"});

// 直接序列化一个Map容器
js["lanuage"] = m;

// 输出序列化后的JSON字符串
cout << js << endl;

return 0;
}

程序运行输出的结果:

1
{"lanuage":[[1,"Go"],[2,"Rust"],[3,"Python"]],"list":[1,2,3]}

案例三

  • C++ 对象与 JSON 对象的转换
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
#include <iostream>
#include "json.hpp"

using namespace std;

// 类型重定义
using json = nlohmann::json;

// 结构体
struct Person {
std::string name;
int age;
std::vector<std::string> hobbies;
};

// C++对象转JSON对象(必须在调用前定义,并且与Person类处于同一命名空间中)
void to_json(json &j, const Person &p) {
j = json{{"name", p.name}, {"age", p.age}, {"hobbies", p.hobbies}};
}

// JSON对象转C++对象(必须在调用前定义,并且与Person类处于同一命名空间中)
void from_json(const json &j, Person &p) {
j.at("name").get_to(p.name);
j.at("age").get_to(p.age);
j.at("hobbies").get_to(p.hobbies);
}

int main() {
// C++对象
Person alice{"Alice", 18, {"reading", "biking"}};

// C++对象转换为JSON对象,依赖显式定义的to_json()函数
json j1 = alice;

// 输出序列化后的JSON字符串,并指定缩进空格数为4
cout << "Person object to JSON object : " << j1.dump(4) << endl;

// JSON对象转换为C++对象,依赖显式定义的from_json()函数
Person alice2 = j1;

// 输出C++对象的成员变量
cout << "name = " << alice2.name << ", age = " << alice2.age << endl;

return 0;
}

程序运行输出的结果:

1
2
3
4
5
6
7
8
9
Person object to JSON object : {
"age": 18,
"hobbies": [
"reading",
"biking"
],
"name": "Alice"
}
name = Alice, age = 18

JSON 反序列化

当从网络接收到的字符串为 JSON 格式时,可以用 JSON 库直接反序列化取得数据或者直接反序列化出对象,甚至 STL 容器。

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
#include <iostream>
#include <vector>
#include "json.hpp"

using namespace std;

// 类型重定义
using json = nlohmann::json;

int main() {
vector<string> v1;
v1.push_back("Go");
v1.push_back("Rust");
v1.push_back("Python");

map<int, string> map1;
map1.insert({1, "Dog"});
map1.insert({2, "Cat"});

// JSON 对象
json j1;
j1["id"] = {1, 2, 3, 4, 5};
j1["name"] = "zhang san";
j1["language"] = v1;
j1["animal"] = map1;

// 模拟从网络接收到JSON字符串(序列化操作)
string jsonStr = j1.dump();
cout << "json string: " << jsonStr << endl;

// 将JSON字符串转换成JSON对象(反序列化操作)
json j2 = json::parse(jsonStr);

// 直接获取key-value
string name = j2["name"];
cout << "name: " << name << endl;

// 直接反序列化Vector容器
vector<string> v2 = j2["language"];
for (const string &str : v2) {
cout << str << " ";
}
cout << endl;

// 直接反序列化Map容器
map<int, string> map2 = j2["animal"];
for (const auto &item : map2) {
cout << item.first << " - " << item.second << " ";
}
cout << endl;

return 0;
}

程序运行输出的结果:

1
2
3
4
json string: {"animal":[[1,"Dog"],[2,"Cat"]],"id":[1,2,3,4,5],"language":["Go","Rust","Python"],"name":"zhang san"}
name: zhang san
Go Rust Python
1 - Dog 2 - Cat