RapidJSON 教程(面向 C++)

7 阅读6分钟

下面给你一个从零到能写项目的 RapidJSON 教程(面向 C++),尽量把你“不熟 JSON/不知道怎么用”的点都补齐:怎么解析、怎么取值、怎么改、怎么生成、怎么读写文件、常见坑。你可以边看边抄代码跑。


0. 你需要先知道的 JSON 基础(很短)

JSON 只有这几种数据类型:

  • object{ "key": value }(键值对)
  • array[ value1, value2 ]
  • string"hello"
  • number1233.14
  • booltrue/false
  • nullnull

示例:

{
  "name": "Alice",
  "age": 18,
  "skills": ["C++", "Linux"],
  "active": true
}

RapidJSON 就是让你在 C++ 里:解析(JSON字符串→C++结构)生成(C++结构→JSON字符串)修改/查询


1. RapidJSON 是什么?(你只要记住两种用法)

RapidJSON 提供两套 API:

  1. DOM(Document/Value) :像操作“树”一样操作 JSON(最常用、最易学)
  2. 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 个练习)

  1. 给定:
{"users":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}

要求:遍历 users,打印 id:name

  1. usersid=2 的 name 改成 "bob",再输出 JSON。
  2. 写一个函数 GetIntOrDefault(doc, "key", default):不存在或类型不对就返回默认值。

如果你愿意,我可以按你的使用场景把例子“定制化”一下:
你是想用 RapidJSON 来做 配置文件HTTP 接口返回、还是 日志/数据落盘?我可以直接给你一套最贴近你场景的代码结构(读取、校验、默认值、版本升级、保存策略)。