集群聊天室项目--json详解
/ 解析时忽略注释(JSON本身无注释,但可兼容//或/* */注释)// 第三个参数:ignore_comments=true// 解析时允许尾逗号(兼容不规范的JSON)// 第四个参数:allow_trailing_commas=trueJSON 的核心是 “2 种结构(对象 / 数组)+6 种类型”,语法规则严格但简单,C++ 中用 nlohmann/json 可实现全量操作。解析 / 序
JSON是一种轻量级、跨语言的数据交换格式.
JSON 核心结构与数据类型
JSON 只有两种基础结构,可嵌套组合出复杂数据:
1. 两种基础结构
| 结构类型 | 语法表示 | 描述 | 示例 |
|---|---|---|---|
| 对象(Object) | {} 包裹,键值对用 : 分隔,键值对间用 , 分隔 |
无序的键值对集合,键必须是字符串(双引号包裹) | {"id": 1001, "name": "zhangsan", "online": true} |
| 数组(Array) | [] 包裹,元素间用 , 分隔 |
有序的元素集合,元素可以是任意 JSON 数据类型 | ["apple", 20, {"color": "red"}] |
(1)对象(Object):无序键值对集合
- 语法:
{}包裹,键值对用:分隔,多个键值对用,分隔; - 强制规则:键必须是双引号包裹的字符串(单引号 / 无引号均非法);值可以是任意 JSON 数据类型;
- 示例(聊天系统用户信息):
{ "id": 1001, "name": "zhangsan", "password": "123456", "online": true, "friends": [1002, 1003], "info": {"age": 20, "gender": "male"} }
(2)数组(Array):有序元素集合
- 语法:
[]包裹,元素间用,分隔; - 规则:元素类型无限制(字符串 / 数字 / 布尔 /null/ 对象 / 数组),允许混合类型;
- 示例(聊天系统离线消息):
[ {"from": 1002, "content": "你好", "time": "2025-12-07"}, {"from": 1003, "content": "在吗?", "time": "2025-12-07"}, ["系统通知", "账号已登录"] ]
2. 支持的数据类型
JSON 的值可以是以下 6 种类型(注意:没有 “注释” 语法,也没有 “undefined”“函数” 等类型):
| 类型 | 描述 | 示例 |
|---|---|---|
| 字符串 | 双引号包裹的 Unicode 字符(不能用单引号) | "hello"、"中文测试" |
| 数字 | 整数 / 浮点数(支持正负、科学计数法) | 123、-45.6、1e3 |
| 布尔值 | 仅 true 或 false(小写) |
true |
| null | 表示空值(小写) | null |
| 对象(Object) | 嵌套的键值对集合 | {"age": 20} |
| 数组(Array) | 嵌套的元素集合 | [1, 2, 3] |
C++ 操作 JSON(nlohmann/json 全量实战)
C++ 标准库无 JSON 支持,nlohmann/json 是工业级首选(单头文件、无需编译、兼容 C++11+、功能最全),以下是其全量操作指南。
1. 环境准备
-
方式 1(推荐):下载单头文件
json.hpp,放入项目目录,直接#include "json.hpp"; -
方式 2(包管理):Ubuntu
sudo apt install nlohmann-json3-dev,CentOSyum install nlohmann-json-devel; -
命名空间简化:
using json = nlohmann::json;。
2. 核心操作:构造 JSON
(1)直接初始化(静态构造)
适合已知数据结构的场景,简洁高效:
#include <iostream>
#include "json.hpp"
using json = nlohmann::json;
using namespace std;
int main() {
// 构造对象
json user = {
{"id", 1001},
{"name", "zhangsan"},
{"online", true},
{"friends", {1002, 1003}}, // 嵌套数组
{"info", {{"age", 20}, {"gender", "male"}}} // 嵌套对象
};
// 构造数组
json offline_msgs = {
{{"from", 1002}, {"content", "你好"}, {"time", "2025-12-07"}},
{{"from", 1003}, {"content", "在吗?"}, {"time", "2025-12-07"}}
};
// 打印(dump() 序列化)
cout << "用户对象:" << user.dump(4) << endl; // 格式化输出(缩进4空格)
cout << "离线消息数组:" << offline_msgs.dump() << endl; // 紧凑输出
return 0;
}
(2)动态构造(运行时添加)
适合数据结构不确定的场景(如根据业务逻辑添加字段):
int main() {
// 空对象 + 动态添加字段
json resp;
resp["code"] = 0; // 数字
resp["msg"] = "success"; // 字符串
resp["data"] = json::object(); // 嵌套空对象
resp["data"]["userid"] = 1001;
resp["data"]["friends"] = json::array(); // 嵌套空数组
resp["data"]["friends"].push_back(1002);
resp["data"]["friends"].push_back(1003);
// 空数组 + 动态添加元素
json arr;
arr.emplace_back("hello"); // 原地构造,比push_back更高效
arr.emplace_back(123);
arr.emplace_back(json::object({{"key", "value"}}));
cout << "动态构造的响应:" << resp.dump(4) << endl;
return 0;
}
(3)从 C++ 容器转换(自动适配)
支持 std::map/std::unordered_map/std::vector/std::list 等容器直接转换为 JSON:
int main() {
// vector → JSON数组
vector<int> vec = {1001, 1002, 1003};
json arr = vec;
cout << "vector转数组:" << arr.dump() << endl; // [1001,1002,1003]
// map → JSON对象
map<string, string> mp = {{"name", "zhangsan"}, {"password", "123456"}};
json obj = mp;
cout << "map转对象:" << obj.dump() << endl; // {"name":"zhangsan","password":"123456"}
return 0;
}
3. 核心操作:解析 JSON(字符串 → JSON 对象 / 数组)
将客户端 / 文件传来的 JSON 字符串解析为可操作的 JSON 对象,必须加异常捕获(避免非法格式导致崩溃)。
(1)基础解析
int main() {
// 模拟网络接收的JSON字符串(聊天系统登录请求)
string req_str = R"({"type":"login","id":"1001","password":"123456"})";
try {
// 解析字符串为JSON对象
json req = json::parse(req_str);
// 访问字段(推荐at(),越界抛异常;[] 越界会自动插入,不推荐)
string type = req.at("type"); // "login"
string id_str = req.at("id"); // "1001"
int id = stoi(id_str); // 字符串转数字
cout << "解析登录请求:type=" << type << ", id=" << id << endl;
} catch (const json::parse_error& e) {
// 解析格式错误(如尾逗号、单引号、语法混乱)
cerr << "JSON解析失败:" << e.what() << ",原始字符串:" << req_str << endl;
} catch (const json::out_of_range& e) {
// 字段不存在(如访问req.at("non_exist_key"))
cerr << "JSON字段不存在:" << e.what() << endl;
} catch (const exception& e) {
// 其他异常(如stoi转换失败)
cerr << "处理失败:" << e.what() << endl;
}
return 0;
}
(2)高级解析:自定义参数
// 解析时忽略注释(JSON本身无注释,但可兼容//或/* */注释)
json req = json::parse(req_str, nullptr, true); // 第三个参数:ignore_comments=true
// 解析时允许尾逗号(兼容不规范的JSON)
json req = json::parse(req_str, nullptr, true, true); // 第四个参数:allow_trailing_commas=true
4. 核心操作:序列化(JSON 对象 → 字符串)
将 JSON 对象 / 数组转换为字符串,用于网络发送 / 文件存储,支持 “紧凑格式” 和 “格式化格式”。
int main() {
json resp = {
{"code", 0},
{"msg", "login success"},
{"data", {{"id", 1001}, {"name", "zhangsan"}}}
};
// 紧凑格式(默认,体积小,适合网络传输)
string resp_compact = resp.dump();
cout << "紧凑格式:" << resp_compact << endl;
// 输出:{"code":0,"msg":"login success","data":{"id":1001,"name":"zhangsan"}}
// 格式化格式(缩进4空格,易读,适合日志/调试)
string resp_pretty = resp.dump(4);
cout << "格式化格式:" << endl << resp_pretty << endl;
/* 输出:
{
"code": 0,
"msg": "login success",
"data": {
"id": 1001,
"name": "zhangsan"
}
}
*/
return 0;
}
5. 核心操作:遍历 JSON
(1)遍历对象(键值对)
int main() {
json user = {{"id", 1001}, {"name", "zhangsan"}, {"online", true}};
// 方式1:范围for(C++17+,推荐)
for (auto& [key, value] : user.items()) {
cout << "键:" << key << ",值:" << value << ",类型:" << value.type_name() << endl;
}
// 方式2:迭代器遍历(兼容C++11+)
for (auto it = user.begin(); it != user.end(); ++it) {
cout << "键:" << it.key() << ",值:" << it.value() << endl;
}
return 0;
}
(2)遍历数组(元素)
int main() {
json arr = {1001, 1002, 1003, {"name", "zhangsan"}};
// 方式1:范围for(推荐)
for (auto& elem : arr) {
cout << "元素:" << elem;
if (elem.is_object()) { // 判断元素类型
cout << "(对象类型)";
}
cout << endl;
}
// 方式2:下标遍历(知道长度)
for (size_t i = 0; i < arr.size(); ++i) {
cout << "索引" << i << ":" << arr.at(i) << endl;
}
return 0;
}
6. 核心操作:修改 / 删除 JSON
int main() {
json user = {{"id", 1001}, {"name", "zhangsan"}, {"friends", {1002, 1003}}};
// 1. 修改字段值
user.at("name") = "张三"; // 字符串修改
user.at("friends").at(0) = 1004; // 数组元素修改
// 2. 添加新字段/元素
user["age"] = 20; // 对象添加字段
user["friends"].push_back(1005); // 数组追加元素
// 3. 删除字段/元素
user.erase("password"); // 删除对象字段(不存在则无操作)
user["friends"].pop_back(); // 删除数组最后一个元素
user["friends"].erase(user["friends"].begin() + 1); // 删除数组指定位置元素
// 4. 清空对象/数组
json empty_obj = json::object(); // 空对象 {}
json empty_arr = json::array(); // 空数组 []
user["friends"].clear(); // 清空数组
cout << "修改后的JSON:" << user.dump(4) << endl;
return 0;
}
7. 核心操作:类型判断与转换
避免类型不匹配导致的崩溃,操作前先判断类型:
int main() {
json data = {
{"id", "1001"}, // 字符串类型的ID
{"age", 20}, // 数字类型的年龄
{"friends", [1002, 1003]}, // 数组
{"info", nullptr} // null
};
// 1. 类型判断(常用API)
cout << "id是否为字符串:" << data["id"].is_string() << endl; // true
cout << "age是否为数字:" << data["age"].is_number() << endl; // true
cout << "friends是否为数组:" << data["friends"].is_array() << endl; // true
cout << "info是否为null:" << data["info"].is_null() << endl; // true
// 2. 安全转换为C++类型
string id_str = data["id"].get<string>(); // 转字符串
int age = data["age"].get<int>(); // 转int
vector<int> friends = data["friends"].get<vector<int>>(); // 数组转vector
// 3. 类型转换异常捕获
try {
int id = data["id"].get<int>(); // 字符串转int,抛异常
} catch (const json::type_error& e) {
cerr << "类型转换失败:" << e.what() << endl;
}
return 0;
}
实战场景:聊天系统中的 JSON 应用
场景 1:客户端 → 服务器:登录请求
// 客户端发送的JSON(字符串):
// {"type":"login","userid":"1001","password":"123456"}
void handleLogin(const string& recv_str) {
try {
json req = json::parse(recv_str);
// 校验必选字段
if (!req.contains("type") || !req.contains("userid") || !req.contains("password")) {
throw runtime_error("缺少必选字段");
}
// 解析字段
string type = req["type"];
int userid = stoi(req["userid"].get<string>());
string password = req["password"];
// 业务逻辑:校验密码、更新登录状态
bool login_ok = checkPassword(userid, password);
// 构造响应
json resp;
if (login_ok) {
resp["code"] = 0;
resp["msg"] = "登录成功";
resp["data"] = {{"userid", userid}, {"name", getUserName(userid)}};
} else {
resp["code"] = -1;
resp["msg"] = "密码错误";
}
// 发送响应给客户端
sendToClient(resp.dump());
} catch (const exception& e) {
json err_resp = {{"code", -2}, {"msg", "登录失败:" + string(e.what())}};
sendToClient(err_resp.dump());
}
}
场景 2:服务器 → 客户端:批量返回离线消息
// 业务逻辑:查询用户1001的离线消息,构造JSON数组响应
json getOfflineMsgResp(int userid) {
json resp;
resp["code"] = 0;
resp["msg"] = "success";
// 从数据库查询离线消息
vector<OfflineMsg> msgs = queryOfflineMsg(userid);
json msg_arr;
for (auto& msg : msgs) {
msg_arr.push_back({
{"from", msg.from_id},
{"content", msg.content},
{"time", msg.create_time}
});
}
resp["offline_msgs"] = msg_arr; // 空数组则返回[],而非null
return resp;
}
// 发送响应:{"code":0,"msg":"success","offline_msgs":[{"from":1002,"content":"你好","time":"2025-12-07"},...]}
string resp_str = getOfflineMsgResp(1001).dump();
sendToClient(resp_str);
场景 3:客户端 → 服务器:批量添加好友(数组)
// 客户端发送的JSON:
// {"type":"batch_add_friend","userid":"1001","friend_ids":["1002","1003","1004"]}
void handleBatchAddFriend(const string& recv_str) {
try {
json req = json::parse(recv_str);
int userid = stoi(req["userid"].get<string>());
json friend_ids = req["friend_ids"];
// 校验是否为数组
if (!friend_ids.is_array()) {
throw runtime_error("friend_ids必须是数组");
}
// 遍历数组,批量添加好友(事务保证原子性)
for (auto& fid : friend_ids) {
if (!fid.is_string()) {
cerr << "好友ID格式错误:" << fid << endl;
continue;
}
int friendid = stoi(fid.get<string>());
addFriendWithTransaction(userid, friendid); // 事务操作
}
// 构造响应
json resp = {{"code", 0}, {"msg", "批量添加好友成功"}};
sendToClient(resp.dump());
} catch (const exception& e) {
json err_resp = {{"code", -1}, {"msg", "批量添加失败:" + string(e.what())}};
sendToClient(err_resp.dump());
}
}
进阶:性能优化与特殊场景
1. 性能优化(高并发场景)
- 避免频繁解析 / 序列化:将解析后的 JSON 对象缓存,而非每次都解析字符串;
- 使用 stream 解析 / 序列化:超大 JSON(如 100MB+)用 stream 避免内存暴涨:
// stream解析(从文件/网络流读取) ifstream ifs("large.json"); json data; ifs >> data; // stream序列化(写入文件/网络流) ofstream ofs("output.json"); ofs << data.dump(4); - 减少拷贝:用
std::move转移 JSON 对象所有权:json createResp() { json resp = {{"code", 0}}; return std::move(resp); // 避免拷贝 }
2. 特殊场景处理
(1)超大 JSON(GB 级)
- 用
nlohmann::json::parser流式解析,逐行 / 逐字段处理,不加载整个 JSON 到内存; - 避免
dump()一次性序列化,分块输出。
(2)二进制 JSON(MessagePack)
nlohmann/json 支持 MessagePack(二进制 JSON,体积更小、解析更快):
// JSON → MessagePack
json data = {{"id", 1001}, {"name", "zhangsan"}};
vector<uint8_t> msgpack_data = json::to_msgpack(data);
// MessagePack → JSON
json data2 = json::from_msgpack(msgpack_data);
(3)兼容不规范 JSON
- 允许尾逗号:
json::parse(str, nullptr, true, true); - 忽略注释:
json::parse(str, nullptr, true); - 单引号转双引号:解析前手动替换
str.replace(str.find("'"), 1, "\"")。
常见错误与解决方案(排错速查)
| 错误类型 | 错误原因 | 解决方案 |
|---|---|---|
| json::parse_error | 1. 尾逗号;2. 单引号;3. 语法混乱;4. 空字符串 | 1. 移除尾逗号;2. 单引号转双引号;3. 校验 JSON 语法;4. 空字符串返回错误 |
| json::out_of_range | 访问不存在的键 / 数组索引越界 | 1. 用 at () 访问;2. 访问前用 contains () 判断键是否存在;3. 数组索引 < size () |
| json::type_error | 类型不匹配(如字符串转 int、数组当对象用) | 1. 操作前用 is_*() 判断类型;2. 显式转换(如先转字符串再 stoi) |
| 中文乱码 | JSON 字符串编码非 UTF-8 | 1. 客户端 / 服务器统一 UTF-8 编码;2. 序列化 / 解析时指定 UTF-8 |
| 数字精度丢失 | 超大数字(如 64 位整数)用数字类型存储 | 1. 超大数字用字符串存储;2. 用 get<uint64_t>() 转换为 64 位整数 |
JSON vs 其他格式(选型参考)
| 格式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JSON | 易读、跨语言、轻量 | 体积比二进制大、无注释 | 网络通信、配置文件、轻量数据 |
| XML | 规范完善、支持注释 | 体积大、解析慢 | 传统系统、SOAP 接口 |
| Protobuf | 体积极小、解析极快 | 不可读、需定义协议 | 高并发 / 高性能网络通信 |
| MessagePack | 二进制 JSON、体积小 | 不可读 | 兼容 JSON 的高性能场景 |
总结
JSON 的核心是 “2 种结构(对象 / 数组)+6 种类型”,语法规则严格但简单,C++ 中用 nlohmann/json 可实现全量操作。在网络通信(如聊天系统)中,需注意:
- 解析 / 序列化必须加异常捕获,避免非法格式导致崩溃;
- 字段访问优先用 at (),避免越界;
- 类型操作前先判断,避免类型不匹配;
- 高并发场景优化解析 / 序列化性能,超大 JSON 用流式处理。
掌握以上内容,可覆盖 99% 的 JSON 使用场景,包括日常开发、高并发优化、排错等全维度需求。
详细介绍一下JSON的语法规则
分享一些在C++中使用nlohmann/json库的代码示例
更多推荐


所有评论(0)