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<std::string, json>,不是传统 map 迭代器。
立即学习“C++免费学习笔记(深入)”;
- 用
j.at("key")替代j["key"]做安全访问 - 解析失败默认抛
json::parse_error异常,建议用try/catch包裹 - 写入文件时注意
j.dump(2)的缩进参数是可选的,不传则无格式化 - 不支持部分老旧编译器(如 GCC 4.8 以下、MSVC 2015 Update 2 以前)
#include <nlohmann/json.hpp>
using json = nlohmann::json;
std::string s = R"({"city":"北京","population":2171})";
try {
json j = json::parse(s);
std::string city = j.at("city").get<std::string>();
int pop = j.at("population").get<int>();
} catch (json::parse_error& e) {
std::cerr << "JSON parse error at byte " << e.byte << std::endl;
}
rapidjson 性能高但 API 设计反直觉
在嵌入式或高频解析场景(如日志预处理、游戏协议解析)中,rapidjson 解析速度通常比 jsoncpp 快 2–3 倍,内存占用更低。但它采用 SAX(事件驱动)和 DOM(树形)双模式,初学者容易混淆 Document 和 Value 的生命周期,且错误码分散(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 <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
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默认抛,jsoncpp和rapidjson用返回值/状态码 - 是否要跨平台静态链接?
jsoncpp需编译,nlohmann/json头文件即用,rapidjson同样头文件但模板深度大,可能拖慢编译 - 是否需 schema 校验或注释支持?三者均不原生支持,得额外引入
json-schema-validator或自行实现
真正难的不是语法,而是当 JSON 来源不可控(比如用户上传、第三方接口)、字段类型不一致(有时是 "123",有时是 123)、含 BOM 或混合编码时,怎么让解析逻辑不崩——这得靠前置清洗 + 类型断言 + fallback 策略,而不是换一个库就能解决。











