nlohmann/json C++使用

发布于:2025-07-05 ⋅ 阅读:(12) ⋅ 点赞:(0)

nlohmann/json 是一个现代、易用且功能强大的 C++ JSON 库,采用单头文件设计,API 设计直观,类似 Python 的字典操作。

项目地址:https://github.com/nlohmann/json

安装方法

简单安装(推荐)

  1. 下载最新版本的 json.hpp

  2. 将头文件放入项目目录

  3. 在代码中包含头文件:

    #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);

最佳实践

  1. 使用 at() 安全访问

    // 使用 at() 而不是 operator[] 可以检查键是否存在
    try {
        auto value = j.at("key");
    } catch (json::out_of_range& e) {
        // 处理键不存在的情况
    }
  2. 优先使用 get_to 转换

    int age;
    person["age"].get_to(age); // 更安全高效的类型转换
  3. 使用迭代器遍历大型对象

    for (auto it = j.begin(); it != j.end(); ++it) {
        std::cout << it.key() << " : " << it.value() << std::endl;
    }
  4. 利用 contains() 检查键存在

    if (j.contains("important_key")) {
        // 处理重要键
    }
  5. 优化大型 JSON 处理

    // 使用 json::parse 的重载避免复制
    json large = json::parse(big_string, nullptr, false);
    if (large.is_discarded()) {
        // 处理解析错误
    }

性能优化技巧

  1. 重用 JSON 对象:避免频繁创建和销毁对象

    // 重用JSON对象减少分配
    thread_local json reusable_buffer;
    
    void process_data(const std::string& input) {
        reusable_buffer = json::parse(input);
        // 处理数据...
    }
  2. 使用 json::object() 和 json::array() 预分配

    json j = json::object(); // 预分配为对象
    j["key"] = "value";
    
    json arr = json::array(); // 预分配为数组
    arr.push_back(1);
  3. 移动语义优化

    json create_large_json() {
        json j;
        // 填充数据...
        return j; // 编译器会使用移动语义优化
    }
  4. 使用二进制格式处理大型数据: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();

网站公告

今日签到

点亮在社区的每一天
去签到