c++中如何解析json数据_c++常用json库的使用方法对比【详解】

jsoncpp是CMake项目中最稳妥的JSON解析方案;需检查parse()返回值、用isMember()防越界、数字统一用asDouble()/asInt()、确保UTF-8编码。

jsoncpp 解析 JSON 字符串最稳妥

如果你的项目已用 CMake 管理,且不强求零依赖或极致性能,jsoncpp 是目前最省心的选择。它语法直观、文档完整、错误提示清晰,对嵌套对象/数组、空值、转义字符支持稳定。

常见错误现象:Json::Value root; 直接访问未解析的 root["key"] 会返回空对象而非报错;若 JSON 格式非法(如多逗号、单引号),parseFromStream 默认静默失败,需检查 parser.parse() 返回值。

  • 必须先调用 parser.parse(json_string, root, &errors),再判断返回值是否为 true
  • 读取字段前用 root.isMember("field") 防止越界
  • 数字类型统一用 asDouble()asInt(),避免隐式转换出错
  • 中文字符串需确保输入是 UTF-8 编码,

    jsoncpp 不做编码转换
Json::CharReaderBuilder builder;
std::string errors;
std::istringstream json_stream(R"({"name":"张三","age":28,"tags":["cpp","json"]})");
Json::Value root;
if (!Json::parseFromStream(builder, json_stream, &root, &errors)) {
    std::cerr << "Parse error: " << errors << std::endl;
    return;
}
std::string name = root["name"].asString();
int age = root["age"].asInt();
for (const auto& tag : root["tags"]) {
    std::cout << tag.asString() << std::endl;
}

nlohmann/json 写法最接近现代 C++ 习惯

头文件即用、无需编译依赖、支持结构体自动映射(JSON_FOR_EACH_NESTED 或自定义 to_json/from_json),适合新项目或快速原型。但要注意:它把 JSON 当作一等公民,所有操作都基于 json 类型,原生不支持直接读取 char*,必须先构造 json j = json::parse(str)

容易踩的坑:j["missing_key"] 会自动创建空值(不是 nullptr),导致误判存在性;j.at("key") 才抛异常;迭代 JSON 对象时,j.items() 返回的是 std::pair<:string json>,不是传统 map 迭代器。

  • j.at("key") 替代 j["key"] 做安全访问
  • 解析失败默认抛 json::parse_error 异常,建议用 try/catch 包裹
  • 写入文件时注意 j.dump(2) 的缩进参数是可选的,不传则无格式化
  • 不支持部分老旧编译器(如 GCC 4.8 以下、MSVC 2015 Update 2 以前)
#include 
using json = nlohmann::json;
std::string s = R"({"city":"北京","population":2171})";
try {
    json j = json::parse(s);
    std::string city = j.at("city").get();
    int pop = j.at("population").get();
} catch (json::parse_error& e) {
    std::cerr << "JSON parse error at byte " << e.byte << std::endl;
}

rapidjson 性能高但 API 设计反直觉

在嵌入式或高频解析场景(如日志预处理、游戏协议解析)中,rapidjson 解析速度通常比 jsoncpp 快 2–3 倍,内存占用更低。但它采用 SAX(事件驱动)和 DOM(树形)双模式,初学者容易混淆 DocumentValue 的生命周期,且错误码分散(kParseErrorNone 等宏需手动查)。

典型问题:用 document.Parse() 后直接 document["key"] 报错,因为没检查 document.IsObject()Value 是非持有型引用,不能脱离 Document 单独存在;字符串字面量要用 GetString(),不能直接赋给 std::string

  • 每次解析后必须调用 document.HasParseError() == false 判断成功
  • 访问前确认类型:用 value.IsString()value.IsArray() 等,而非仅靠字段名
  • 获取字符串必须用 value.GetString(),且其返回指针依赖 Document 生命周期
  • 启用 RAPIDJSON_HAS_STDSTRING 宏才能用 std::string 构造器
#define RAPIDJSON_HAS_STDSTRING 1
#include 
#include 
#include 
rapidjson::Document document;
if (document.Parse(R"({"score":95.5,"passed":true})").HasParseError()) {
    std::cerr << "Offset " << document.GetErrorOffset() << std::endl;
    return;
}
double score = document["score"].GetDouble();
bool passed = document["passed"].GetBool();

选库前必须核对这三点

很多项目卡在选型上,其实只需明确三个事实:

  • 是否允许运行时抛异常?nlohmann/json 默认抛,jsoncpprapidjson 用返回值/状态码
  • 是否要跨平台静态链接?jsoncpp 需编译,nlohmann/json 头文件即用,rapidjson 同样头文件但模板深度大,可能拖慢编译
  • 是否需 schema 校验或注释支持?三者均不原生支持,得额外引入 json-schema-validator 或自行实现

真正难的不是语法,而是当 JSON 来源不可控(比如用户上传、第三方接口)、字段类型不一致(有时是 "123",有时是 123)、含 BOM 或混合编码时,怎么让解析逻辑不崩——这得靠前置清洗 + 类型断言 + fallback 策略,而不是换一个库就能解决。