nlohmann/json 是一个现代、易用且功能强大的 C++ JSON 库,采用单头文件设计,API 设计直观,类似 Python 的字典操作。
项目地址:https://github.com/nlohmann/json
安装方法
简单安装(推荐)
下载最新版本的 json.hpp
将头文件放入项目目录
在代码中包含头文件:
#include "json.hpp" using json = nlohmann::json; // 使用别名简化代码
包管理器安装
vcpkg:
bash
vcpkg install nlohmann-json
Conan:
bash
conan install nlohmann-json/3.11.2
基础用法
1. 创建 JSON 对象
// 创建空对象
json j;
// 直接初始化
json person = {
{"name", "Alice"},
{"age", 30},
{"is_student", false},
{"courses", {"Math", "Physics", "Chemistry"}},
{"address", {
{"street", "123 Main St"},
{"city", "New York"},
{"zip", 10001}
}}
};
2. 序列化与反序列化
// 序列化为字符串
std::string json_str = person.dump(); // 紧凑格式
std::string pretty_str = person.dump(4); // 带缩进的格式
// 反序列化
json parsed = json::parse(json_str);
// 文件操作
std::ofstream("person.json") << person; // 写入文件
std::ifstream("person.json") >> person; // 从文件读取
3. 访问数据
// 直接访问
std::string name = person["name"];
int age = person["age"];
// 安全访问(避免异常)
std::string city = person["address"].value("city", "Unknown");
int height = person.value("height", 170); // 默认值
// 检查键是否存在
if (person.contains("courses")) {
// 处理课程数据
}
// 迭代访问
for (auto& course : person["courses"]) {
std::cout << course.get<std::string>() << std::endl;
}
// 访问嵌套对象
std::string street = person["address"]["street"];
4. 修改数据
// 修改值
person["age"] = 31;
person["address"]["zip"] = 10002;
// 添加新键
person["email"] = "alice@example.com";
// 添加数组元素
person["courses"].push_back("Biology");
// 删除键
person.erase("is_student");
// 合并对象
json extra_info = {{"hobbies", {"reading", "hiking"}};
person.update(extra_info);
进阶用法
1. 自定义类型转换
struct Employee {
std::string name;
int id;
double salary;
};
// 实现自定义类型转换
void to_json(json& j, const Employee& e) {
j = json{{"name", e.name}, {"id", e.id}, {"salary", e.salary}};
}
void from_json(const json& j, Employee& e) {
j.at("name").get_to(e.name);
j.at("id").get_to(e.id);
j.at("salary").get_to(e.salary);
}
// 使用
Employee emp{"Bob", 1001, 75000.0};
json j_emp = emp; // 自动转换
Employee emp2 = j_emp.get<Employee>(); // 反向转换
2. 处理异常
try {
json j = json::parse(invalid_json);
int value = j.at("nonexistent_key");
} catch (const json::parse_error& e) {
std::cerr << "解析错误: " << e.what() << std::endl;
} catch (const json::out_of_range& e) {
std::cerr << "范围错误: " << e.what() << std::endl;
} catch (const json::type_error& e) {
std::cerr << "类型错误: " << e.what() << std::endl;
}
3. 使用 JSON 指针
json person = {
{"name", "Alice"},
{"address", {
{"city", "New York"},
{"zip", 10001}
}}
};
// 使用 JSON 指针访问嵌套值
auto zip_code = person.at("/address/zip"_json_pointer).get<int>();
// 修改值
person["/address/city"_json_pointer] = "Boston";
4. 二进制序列化(CBOR/MessagePack)
// 序列化为 CBOR
std::vector<uint8_t> cbor = json::to_cbor(person);
// 从 CBOR 反序列化
json from_cbor = json::from_cbor(cbor);
// MessagePack 类似
std::vector<uint8_t> msgpack = json::to_msgpack(person);
json from_msgpack = json::from_msgpack(msgpack);
5. 中文UTF-8
//基本读写处理
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main() {
// 创建包含中文的 JSON
json j = {
{"姓名", "张三"},
{"年龄", 25},
{"地址", "北京市海淀区"},
{"描述", "这是一个UTF-8中文测试"}
};
// 序列化为字符串(自动使用UTF-8)
std::string json_str = j.dump(4);
std::cout << "序列化结果:\n" << json_str << std::endl;
// 反序列化
json parsed = json::parse(json_str);
std::cout << "姓名: " << parsed["姓名"].get<std::string>() << std::endl;
return 0;
}
//文件操作中的编码处理
#include <fstream>
// 写入UTF-8文件
void write_json_file(const std::string& filename, const json& data) {
std::ofstream out(filename, std::ios::binary); // 二进制模式保证编码不变
if (out) {
// 写入UTF-8 BOM(可选)
out << "\xEF\xBB\xBF";
out << data.dump(4);
}
}
// 读取UTF-8文件
json read_json_file(const std::string& filename) {
std::ifstream in(filename, std::ios::binary);
if (in) {
// 跳过BOM(如果存在)
if (in.peek() == 0xEF) {
in.get(); in.get(); in.get(); // 跳过EF BB BF
}
std::string content((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
return json::parse(content);
}
return json();
}
//控制台输出乱码问题(Windows)
#ifdef _WIN32
#include <windows.h>
#endif
void setup_console_encoding() {
#ifdef _WIN32
// 设置控制台为UTF-8编码
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
#endif
}
int main() {
setup_console_encoding();
json j = {{"测试", "中文内容"}};
std::cout << j.dump() << std::endl;
}
//中文字符串处理注意事项
// 正确获取中文键值
json j = {{"姓名", "李四"}};
// 安全访问方法
if (j.contains("姓名")) {
// 使用get<std::string>()确保类型安全
std::string name = j["姓名"].get<std::string>();
std::cout << "姓名长度(字节): " << name.size() << std::endl;
std::cout << "实际字符数: " << get_utf8_char_count(name) << std::endl;
}
// UTF-8字符计数函数
size_t get_utf8_char_count(const std::string& str) {
size_t count = 0;
for (char c : str) {
// 统计非连续字节 (UTF-8字符的第一个字节)
if ((c & 0xC0) != 0x80) count++;
}
return count;
}
//UTF-8与UTF-16转换
#include <locale>
#include <codecvt>
// UTF-8与UTF-16转换
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::wstring wide = converter.from_bytes(utf8_str);
std::string utf8 = converter.to_bytes(wide_str);
最佳实践
使用
at()
安全访问:// 使用 at() 而不是 operator[] 可以检查键是否存在 try { auto value = j.at("key"); } catch (json::out_of_range& e) { // 处理键不存在的情况 }
优先使用
get_to
转换:int age; person["age"].get_to(age); // 更安全高效的类型转换
使用迭代器遍历大型对象:
for (auto it = j.begin(); it != j.end(); ++it) { std::cout << it.key() << " : " << it.value() << std::endl; }
利用
contains()
检查键存在:if (j.contains("important_key")) { // 处理重要键 }
优化大型 JSON 处理:
// 使用 json::parse 的重载避免复制 json large = json::parse(big_string, nullptr, false); if (large.is_discarded()) { // 处理解析错误 }
性能优化技巧
重用 JSON 对象:避免频繁创建和销毁对象
// 重用JSON对象减少分配 thread_local json reusable_buffer; void process_data(const std::string& input) { reusable_buffer = json::parse(input); // 处理数据... }
使用
json::object()
和json::array()
预分配:json j = json::object(); // 预分配为对象 j["key"] = "value"; json arr = json::array(); // 预分配为数组 arr.push_back(1);
移动语义优化:
json create_large_json() { json j; // 填充数据... return j; // 编译器会使用移动语义优化 }
使用二进制格式处理大型数据:CBOR 或 MessagePack 比文本 JSON 更高效
常见问题解决
问题1:未定义引用错误
确保包含正确的头文件
如果使用 CMake,添加:
cmake
target_link_libraries(your_target PRIVATE nlohmann_json::nlohmann_json)
问题2:类型转换错误
try {
int number = j["string_value"].get<int>();
} catch (const json::type_error& e) {
// 处理类型不匹配
}
问题3:处理空值
json j = {{"value", nullptr}};
if (j["value"].is_null()) {
// 处理空值
}
问题4:处理特殊数值
json j = {{"inf", INFINITY}, {"nan", NAN}};
j["inf"] = std::numeric_limits<double>::infinity();
j["nan"] = std::numeric_limits<double>::quiet_NaN();