下面给你一个从零到能写项目的 RapidJSON 教程(面向 C++),尽量把你“不熟 JSON/不知道怎么用”的点都补齐:怎么解析、怎么取值、怎么改、怎么生成、怎么读写文件、常见坑。你可以边看边抄代码跑。
0. 你需要先知道的 JSON 基础(很短)
JSON 只有这几种数据类型:
- object:
{ "key": value }(键值对) - array:
[ value1, value2 ] - string:
"hello" - number:
123、3.14 - bool:
true/false - null:
null
示例:
{
"name": "Alice",
"age": 18,
"skills": ["C++", "Linux"],
"active": true
}
RapidJSON 就是让你在 C++ 里:解析(JSON字符串→C++结构) 、生成(C++结构→JSON字符串) 、修改/查询。
1. RapidJSON 是什么?(你只要记住两种用法)
RapidJSON 提供两套 API:
- DOM(Document/Value) :像操作“树”一样操作 JSON(最常用、最易学)
- SAX(Reader/Handler) :流式事件回调(超大 JSON / 极致性能才用)
本教程主要讲 DOM,后面再给你一个 SAX 的入门例子。
2. 安装/引入(最稳的方式)
RapidJSON 是头文件库(header-only) ,最简单就是把源码放进项目,然后 #include。
方法 A:直接把 rapidjson 放进工程
把 rapidjson 仓库里的 include/rapidjson 整个目录拷贝到你项目里(或作为子模块),然后编译时保证 include 路径能找到它。
代码里这样用:
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
方法 B:CMake(把 include 目录加进去)
target_include_directories(your_target PRIVATE /path/to/rapidjson/include)
你如果用 vcpkg/Conan 也可以,但不同环境命令不一样;先用“拷贝 include”最不容易卡住。
3. 第一个程序:解析 JSON 字符串并取值(必学)
3.1 解析 + 错误处理
#include <iostream>
#include <string>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
int main() {
const char* json = R"({
"name": "Alice",
"age": 18,
"skills": ["C++", "Linux"],
"active": true
})";
rapidjson::Document d;
d.Parse(json);
if (d.HasParseError()) {
std::cout << "Parse error: "
<< rapidjson::GetParseError_En(d.GetParseError())
<< " at offset " << d.GetErrorOffset() << "\n";
return 1;
}
// JSON 根必须是 object 才能用 key 查
if (!d.IsObject()) {
std::cout << "Root is not an object\n";
return 1;
}
std::cout << "OK\n";
}
3.2 安全取值(强烈建议这样写)
if (d.HasMember("name") && d["name"].IsString()) {
std::cout << d["name"].GetString() << "\n";
}
if (d.HasMember("age") && d["age"].IsInt()) {
std::cout << d["age"].GetInt() << "\n";
}
if (d.HasMember("active") && d["active"].IsBool()) {
std::cout << std::boolalpha << d["active"].GetBool() << "\n";
}
3.3 取数组
if (d.HasMember("skills") && d["skills"].IsArray()) {
const auto& arr = d["skills"].GetArray();
for (auto& v : arr) {
if (v.IsString()) std::cout << v.GetString() << "\n";
}
}
4. 遍历 object 的所有键值(配置文件很常用)
for (auto it = d.MemberBegin(); it != d.MemberEnd(); ++it) {
std::cout << "key=" << it->name.GetString() << " ";
const auto& val = it->value;
if (val.IsString()) std::cout << "string=" << val.GetString();
else if (val.IsInt()) std::cout << "int=" << val.GetInt();
else if (val.IsBool()) std::cout << "bool=" << val.GetBool();
else if (val.IsArray()) std::cout << "array(size)=" << val.Size();
else if (val.IsObject()) std::cout << "object";
else if (val.IsNull()) std::cout << "null";
std::cout << "\n";
}
5. 生成 JSON(从 C++ 拼出 JSON 字符串)
RapidJSON 生成 JSON 常见两种:
- Writer:紧凑输出(无缩进)
- PrettyWriter:漂亮输出(有缩进,适合配置文件)
5.1 用 Writer/PrettyWriter 直接写(最直观)
#include <rapidjson/prettywriter.h>
#include <rapidjson/stringbuffer.h>
#include <iostream>
int main() {
rapidjson::StringBuffer sb;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(sb);
writer.StartObject();
writer.Key("name");
writer.String("Alice");
writer.Key("age");
writer.Int(18);
writer.Key("skills");
writer.StartArray();
writer.String("C++");
writer.String("Linux");
writer.EndArray();
writer.Key("active");
writer.Bool(true);
writer.EndObject();
std::cout << sb.GetString() << "\n";
}
6. 修改 JSON(DOM 模式里最容易踩坑的地方:Allocator)
RapidJSON 的 DOM 修改需要用 allocator(内存分配器)。记住一句话:
往 Document 里塞字符串/成员时,通常要用
d.GetAllocator()。
6.1 修改已有字段
rapidjson::Document d;
d.Parse(R"({"age":18})");
d["age"].SetInt(19); // 直接改 int 没问题
6.2 添加新字段(字符串要 allocator)
rapidjson::Document d;
d.SetObject(); // 先让根变 object
auto& alloc = d.GetAllocator();
d.AddMember("age", 18, alloc);
// 添加 string:推荐用 Value 再 SetString
rapidjson::Value name;
name.SetString("Alice", alloc);
d.AddMember("name", name, alloc);
6.3 添加数组/对象
rapidjson::Value skills(rapidjson::kArrayType);
skills.PushBack(rapidjson::Value().SetString("C++", alloc), alloc);
skills.PushBack(rapidjson::Value().SetString("Linux", alloc), alloc);
d.AddMember("skills", skills, alloc);
6.4 删除字段
d.RemoveMember("age");
7. 把 Document 输出成字符串(保存/打印)
你已经有了 Document d,想把它变成 JSON 文本:
#include <rapidjson/stringbuffer.h>
#include <rapidjson/prettywriter.h>
rapidjson::StringBuffer sb;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(sb);
d.Accept(writer);
std::cout << sb.GetString() << "\n";
8. 文件读写(真实项目必备)
8.1 读文件 → 解析(FileReadStream)
#include <cstdio>
#include <rapidjson/document.h>
#include <rapidjson/filereadstream.h>
#include <rapidjson/error/en.h>
#include <iostream>
bool LoadJsonFile(const char* path, rapidjson::Document& d) {
FILE* fp = std::fopen(path, "rb");
if (!fp) return false;
char buffer[65536];
rapidjson::FileReadStream is(fp, buffer, sizeof(buffer));
d.ParseStream(is);
std::fclose(fp);
if (d.HasParseError()) {
std::cout << "Parse error: "
<< rapidjson::GetParseError_En(d.GetParseError())
<< " offset=" << d.GetErrorOffset() << "\n";
return false;
}
return true;
}
8.2 写文件(FileWriteStream)
#include <cstdio>
#include <rapidjson/filewritestream.h>
#include <rapidjson/prettywriter.h>
bool SaveJsonFile(const char* path, const rapidjson::Document& d) {
FILE* fp = std::fopen(path, "wb");
if (!fp) return false;
char buffer[65536];
rapidjson::FileWriteStream os(fp, buffer, sizeof(buffer));
rapidjson::PrettyWriter<rapidjson::FileWriteStream> writer(os);
d.Accept(writer);
std::fclose(fp);
return true;
}
9. 常见坑(你大概率会遇到)
坑 1:GetString() 返回的指针什么时候失效?
GetString() 返回的是Document 内部存储的指针。只要 Document 还活着就有效;Document 析构/重新 Parse 后就失效。
坑 2:SetString("xxx") 为什么要 allocator?
因为 RapidJSON 需要把字符串内容放到 Document 管理的内存里。正确写法:
auto& alloc = d.GetAllocator();
rapidjson::Value v;
v.SetString("hello", alloc);
坑 3:从 std::string 塞进去要注意长度
如果字符串里可能有 \0 或想显式长度,用:
std::string s = "hello";
v.SetString(s.c_str(), static_cast<rapidjson::SizeType>(s.size()), alloc);
坑 4:取值前一定判断类型
不要直接 d["age"].GetInt(),否则类型不对会触发断言(Debug 下直接崩)。
坑 5:operator[] 的前提
d必须是 object- key 必须存在(不存在会创建?在 Document/Value 的
operator[]行为容易误用)
更安全:HasMember()+FindMember()。
10. 进阶但很实用:JSON Pointer(像路径一样取值)
假设 JSON:
{ "a": { "b": [10, 20] } }
用指针取 /a/b/1:
#include <rapidjson/pointer.h>
rapidjson::Document d;
d.Parse(R"({"a":{"b":[10,20]}})");
const rapidjson::Value* v = rapidjson::Pointer("/a/b/1").Get(d);
if (v && v->IsInt()) {
std::cout << v->GetInt() << "\n"; // 20
}
11. SAX 入门(知道有这个就行)
当 JSON 超大,DOM 放不下内存时,用 SAX(事件流)。下面例子:统计出现了多少个 string。
#include <rapidjson/reader.h>
#include <rapidjson/filereadstream.h>
#include <cstdio>
#include <iostream>
struct CountStringHandler : rapidjson::BaseReaderHandler<rapidjson::UTF8<>, CountStringHandler> {
int count = 0;
bool String(const char*, rapidjson::SizeType, bool) { ++count; return true; }
};
int main() {
FILE* fp = std::fopen("big.json", "rb");
if (!fp) return 1;
char buffer[65536];
rapidjson::FileReadStream is(fp, buffer, sizeof(buffer));
rapidjson::Reader reader;
CountStringHandler handler;
rapidjson::ParseResult ok = reader.Parse(is, handler);
std::fclose(fp);
if (!ok) {
std::cout << "Parse failed\n";
return 1;
}
std::cout << "string count = " << handler.count << "\n";
}
12. 给你一个“项目级”小模板(读配置→修改→保存)
#include <iostream>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/filereadstream.h>
#include <rapidjson/filewritestream.h>
#include <cstdio>
bool LoadJsonFile(const char* path, rapidjson::Document& d) {
FILE* fp = std::fopen(path, "rb");
if (!fp) return false;
char buffer[65536];
rapidjson::FileReadStream is(fp, buffer, sizeof(buffer));
d.ParseStream(is);
std::fclose(fp);
if (d.HasParseError()) {
std::cout << "Parse error: " << rapidjson::GetParseError_En(d.GetParseError())
<< " offset=" << d.GetErrorOffset() << "\n";
return false;
}
return true;
}
bool SaveJsonFile(const char* path, const rapidjson::Document& d) {
FILE* fp = std::fopen(path, "wb");
if (!fp) return false;
char buffer[65536];
rapidjson::FileWriteStream os(fp, buffer, sizeof(buffer));
rapidjson::PrettyWriter<rapidjson::FileWriteStream> writer(os);
d.Accept(writer);
std::fclose(fp);
return true;
}
int main() {
rapidjson::Document d;
if (!LoadJsonFile("config.json", d)) {
std::cout << "load failed\n";
return 1;
}
if (!d.IsObject()) {
std::cout << "config root must be object\n";
return 1;
}
auto& alloc = d.GetAllocator();
// 读取
int port = 0;
if (d.HasMember("port") && d["port"].IsInt()) port = d["port"].GetInt();
std::cout << "port=" << port << "\n";
// 修改/补充
d.RemoveMember("last_run");
rapidjson::Value lastRun;
lastRun.SetString("2026-01-04", alloc);
d.AddMember("last_run", lastRun, alloc);
// 保存
if (!SaveJsonFile("config_out.json", d)) {
std::cout << "save failed\n";
return 1;
}
std::cout << "saved\n";
}
13. 你接下来怎么练(3 个练习)
- 给定:
{"users":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}
要求:遍历 users,打印 id:name。
- 把
users里id=2的 name 改成"bob",再输出 JSON。 - 写一个函数
GetIntOrDefault(doc, "key", default):不存在或类型不对就返回默认值。
如果你愿意,我可以按你的使用场景把例子“定制化”一下:
你是想用 RapidJSON 来做 配置文件、HTTP 接口返回、还是 日志/数据落盘?我可以直接给你一套最贴近你场景的代码结构(读取、校验、默认值、版本升级、保存策略)。