前言把内存中的std::map、std::vector之类容器序列化成 JSON是服务端和配置读写里最常见的操作之一。rapidjson 是一个只有头文件的 C JSON 库解析和生成都快、依赖少但它的对象模型和「随手拼字符串」很不一样所有值都挂在rapidjson::Value上字符串、对象、数组的成员在添加时都要显式传入分配器allocator。初学者最容易踩的坑是字符串生命周期。rapidjson::StringRef只保存指针、不复制内容如果传入的是临时std::string的c_str()表达式结束后指针就悬垂了序列化时读到的是已被释放的内存。另一个常见问题是忘记doc.SetObject()或SetArray()直接AddMember得到的是未定义行为。本文按「对象模型 → map 转对象 → 数组与嵌套 → 序列化输出」的顺序讲代码以 C17 为基准。rapidjson 是第三方库具体接口以其官方文档为准下文的调用均采用其稳定版本中长期存在的形式。一、对象模型Document、Value 与 Allocatorrapidjson::Document本身继承自rapidjson::Value可以当作根节点用。一个Value有类型kNullType、kFalseType、kTrueType、kObjectType、kArrayType、kStringType、kNumberType。对象和数组在添加成员前必须先确定类型// 适用于 C17rapidjson::Document doc;doc.SetObject(); // 根节点是对象rapidjson::Document::AllocatorType alloc doc.GetAllocator();分配器负责所有内部内存成员数组、字符串副本。它的生命周期必须覆盖doc的使用期所以通常直接从doc取引用不要自己另建一个。所有会「新增内容」的调用——AddMember、PushBack、用Value构造字符串——都要把alloc传进去。需要牢记的一点Value的拷贝是浅拷贝语义上的「移动式」赋值把子Value加进父节点后子对象的所有权归父节点不应再独立使用。这也是 rapidjson 用分配器统一管理内存的原因。二、把 std::map 组装成 JSON 对象std::map的键是std::string值可能是整数、字符串或更复杂的结构。转成 JSON 对象时有两件事要注意键必须复制值按类型分派。// 适用于 C17#include map#include string#include rapidjson/document.hvoid addScores(rapidjson::Document doc, const std::mapstd::string, int scores) {rapidjson::Document::AllocatorType alloc doc.GetAllocator();for (const auto kv : scores) {// 正确Value 的字符串构造函数会按 alloc 复制一份rapidjson::Value key(kv.first.c_str(), alloc);doc.AddMember(key, kv.second, alloc);}}为什么不能写doc.AddMember(rapidjson::StringRef(kv.first.c_str()), kv.second, alloc)因为StringRef带有「不复制」的标记只记住指针。循环进行到下一轮时kv.first仍有效但函数返回后 map 一旦被修改或销毁JSON 里所有键都会指向失效内存。用Value(const char*, Allocator)构造则会立刻复制代价是一次字符串拷贝换来的是安全。值类型是字符串时同理// 适用于 C17rapidjson::Value val(kv.second.c_str(), alloc);doc.AddMember(key, val, alloc);整数、double、bool有直接接受标量的AddMember重载doc.AddMember(key, 42, alloc)即可不需要先包一层Value。如果 map 的值本身是std::vectorint就要先构造一个数组Value再把它作为成员加入// 适用于 C17rapidjson::Value nums(rapidjson::kArrayType);for (int x : kv.second) {nums.PushBack(x, alloc);}doc.AddMember(key, nums, alloc);PushBack对基本类型有重载对字符串则需要显式构造Value或使用接受StringRef的重载——同样要注意生命周期字面量是安全的临时std::string不安全。三、数组与嵌套结构数组用kArrayType初始化然后逐个PushBack。数组元素是对象时先构造局部对象再把整个对象推进数组。// 适用于 C17#include string#include vector#include rapidjson/document.hstruct Student {std::string name;int score;std::vectorstd::string tags;};void addStudents(rapidjson::Document doc, const std::vectorStudent list) {rapidjson::Document::AllocatorType alloc doc.GetAllocator();rapidjson::Value arr(rapidjson::kArrayType);arr.Reserve(list.size(), alloc); // 可选预分配减少扩容for (const Student s : list) {rapidjson::Value obj(rapidjson::kObjectType);obj.AddMember(name, rapidjson::Value(s.name.c_str(), alloc), alloc);obj.AddMember(score, s.score, alloc);rapidjson::Value tags(rapidjson::kArrayType);for (const std::string t : s.tags) {tags.PushBack(rapidjson::Value(t.c_str(), alloc), alloc);}obj.AddMember(tags, tags, alloc);arr.PushBack(obj, alloc);}doc.AddMember(students, arr, alloc);}这里的name、score、tags都是字符串字面量具有静态存储期用StringRef语义传入是安全的。obj、tags、arr都是局部变量PushBack/AddMember之后内容已经被搬进父节点局部变量离开作用域只是析构一个空壳不会影响结果。嵌套层次不要过深。rapidjson 内部使用递归处理极端深的嵌套会消耗栈空间同时业务上超过三四层的 JSON 通常意味着数据结构该重新设计了。如果要往已有对象里追加而不是新建注意AddMember遇到同名键不会覆盖而是产生重复键。需要覆盖时应先RemoveMember或改用doc[key] newValue这种下标赋值方式下标赋值在键不存在时会创建存在时替换。四、序列化输出组装完成后用Writer或PrettyWriter把Document写进StringBuffer// 适用于 C17#include iostream#include string#include rapidjson/stringbuffer.h#include rapidjson/writer.h#include rapidjson/prettywriter.hstd::string toJson(const rapidjson::Document doc) {rapidjson::StringBuffer buffer;rapidjson::Writerrapidjson::StringBuffer writer(buffer);doc.Accept(writer);return std::string(buffer.GetString(), buffer.GetSize());}std::string toPrettyJson(const rapidjson::Document doc) {rapidjson::StringBuffer buffer;rapidjson::PrettyWriterrapidjson::StringBuffer writer(buffer);writer.SetIndent( , 2);doc.Accept(writer);return std::string(buffer.GetString(), buffer.GetSize());}Accept是Value的 const 成员函数因此可以在 const 引用上调用。它的模板参数是处理器类型Writer与PrettyWriter都满足要求。buffer.GetSize()比strlen更可靠JSON 字符串理论上可以包含被转义的内容用显式长度构造std::string是更稳妥的习惯。五、完整示例// 适用于 C17#include iostream#include map#include string#include vector#include rapidjson/document.h#include rapidjson/stringbuffer.h#include rapidjson/prettywriter.hint main() {rapidjson::Document doc;doc.SetObject();rapidjson::Document::AllocatorType alloc doc.GetAllocator();// 1. map - 对象std::mapstd::string, int scores{{alice, 90}, {bob, 85}, {carol, 77}};rapidjson::Value scoreObj(rapidjson::kObjectType);for (const auto kv : scores) {rapidjson::Value key(kv.first.c_str(), alloc);scoreObj.AddMember(key, kv.second, alloc);}doc.AddMember(scores, scoreObj, alloc);// 2. vector - 数组std::vectorstd::string names{alice, bob, carol};rapidjson::Value nameArr(rapidjson::kArrayType);for (const std::string n : names) {nameArr.PushBack(rapidjson::Value(n.c_str(), alloc), alloc);}doc.AddMember(names, nameArr, alloc);// 3. 数组套对象rapidjson::Value list(rapidjson::kArrayType);for (const auto kv : scores) {rapidjson::Value item(rapidjson::kObjectType);item.AddMember(name, rapidjson::Value(kv.first.c_str(), alloc), alloc);item.AddMember(score, kv.second, alloc);list.PushBack(item, alloc);}doc.AddMember(list, list, alloc);rapidjson::StringBuffer buffer;rapidjson::PrettyWriterrapidjson::StringBuffer writer(buffer);writer.SetIndent( , 2);doc.Accept(writer);std::cout buffer.GetString() std::endl;return 0;}按标准语义和 rapidjson 的文档描述输出应当是一个包含scores、names、list三个成员的对象其中list的顺序由std::map的键序决定alice、bob、carol。若把std::map换成std::unordered_map键的顺序就不再保证JSON 文本会随实现变化——这对需要做文本比对或缓存的场景很关键。常见坑点1. 用 StringRef 绑定临时字符串❌doc.AddMember(rapidjson::StringRef(name.c_str()), 1, alloc);name是局部std::string。 ✅rapidjson::Value key(name.c_str(), alloc); doc.AddMember(key, 1, alloc);让分配器复制。2. 忘记设置根节点类型❌rapidjson::Document doc; doc.AddMember(a, 1, alloc);默认是 null 类型。 ✅ 先doc.SetObject();或doc.SetArray();。3. 把同一把分配器用在不同 Document 上❌ 用docA.GetAllocator()给docB添加成员。 ✅ 每个 Document 的元素只使用自己的分配器跨文档要复制值而不是共享分配器。**4. 直接把 std::string 传给需要 const char* 的接口**❌rapidjson::Value v(s, alloc);其中s是std::string没有这个重载。 ✅rapidjson::Value v(s.c_str(), s.size(), alloc);或rapidjson::Value v(s.c_str(), alloc);。5. 认为 AddMember 会覆盖同名键❌ 连续两次AddMember(id, ...)结果 JSON 里出现两个id。 ✅ 覆盖用doc[id] value;或先doc.RemoveMember(id);。6. 序列化时缓冲区提前销毁❌ 返回const char*指向局部StringBuffer的内容。 ✅ 返回std::string(buffer.GetString(), buffer.GetSize());把内容拷出来。7. 用 strlen 计算 JSON 长度❌std::string s(buffer.GetString());遇到内嵌零字节会截断。 ✅ 用GetSize()显式给出长度。8. 忽略浮点数的输出精度❌ 以为double会原样输出所有十进制位。 ✅ rapidjson 默认按最短可回读的形式输出需要固定精度时用Writer::SetMaxDecimalPlaces并注意它影响的是所有浮点成员。总结任务关键调用注意点根节点设为对象doc.SetObject()忘记会得到 null 类型添加整数成员doc.AddMember(key, 42, alloc)key 用 Value 拷贝添加字符串成员Value(s.c_str(), alloc)不要用悬垂的 StringRef构造数组Value v(kArrayType)逐个PushBack嵌套对象先建局部 Value 再加入父节点加入后不再单独使用输出紧凑文本WriterStringBuffer用GetSize()取长度输出可读文本PrettyWriterSetIndent缩进不影响语义rapidjson 的组装逻辑可以概括成一句话先定类型再逐层添加字符串一律让分配器复制。把字符串生命周期这件事处理对剩下的接口都很直白。对性能敏感的场景可以复用StringBuffer、预先Reserve但不要为了省一次拷贝把生命周期问题引进来。
企业数字化 ERP 产品动态
相关推荐
WorkBuddy实战:AI任务栏、连接器与自动化任务配置指南 1. 先把整套东西的骨架想清楚我大概是去年下半年把 WorkBuddy 装进日常主力机的。最初动机特别朴素——我想要一个按一下快捷键就能弹出来、还能顺手读到我桌面上一堆草稿的输入框。用了两周之后我发现,真正拖慢效率的从来不是“问它什么”,而是“它拿不… · 2026/11/4 8:53:26
PostgreSQL角色管理:pg_user与pg_roles视图对比解析 1. 问题背景:为什么需要关注PostgreSQL角色视图在PostgreSQL数据库管理中,角色(Role)是权限体系的核心概念。无论是用户账号还是组权限,最终都通过角色机制实现。管理员日常最频繁的操作之一就是查询角色信息ÿ… · 2026/11/4 8:52:24
SqlServer教室信息管理系统课程设计:从建库到冲突检测实战 简介:这份资源是面向高校数据库课程设计场景的教室信息管理系统 SQL 脚本合集,适合正在完成数据库课设、需要参考建库建表与业务逻辑实现的学生,也适合想借真实案例巩固 SQL Server 综合应用的初学者。压缩包共 6 个文件,全部为 s… · 2026/11/4 9:27:32
谷歌开源ARTEMIS:让AI像人一样操作手机的移动端测试框架 最近在梳理移动端自动化测试工具链,发现圈子里讨论最多的就是谷歌开源的ARTEMIS框架。说实话,第一次看到这个项目的时候,我的第一反应是“又一个移动端自动化测试框架”,但仔细了解之后发现,它跟我以前用的Appium、UI … · 2026/11/4 9:27:32
即时通讯系统好友管理模块架构设计与高并发优化 1. 项目背景与核心价值即时通讯(IM)系统中的好友管理模块,就像现实生活中的通讯录一样重要。这个看似基础的功能,实际上承载着用户社交关系的核心数据。我在参与多个IM系统开发后发现,好友管理服务的稳定性直接决定了用… · 2026/11/4 9:27:32
WizTree 2026版:极速磁盘分析工具深度指南 1. 为什么你需要WizTree这款神器作为一名长期与磁盘空间搏斗的老鸟,我必须说WizTree彻底改变了我管理硬盘的方式。这款工具能在3秒内扫描完1TB硬盘,比Windows自带的磁盘分析工具快上百倍。它直接读取NTFS文件系统的MFT(主文件表)&… · 2026/11/4 9:27:32
10万星开源AI Agent项目工程架构拆解:工作流引擎与自托管实践 这个项目能到 10 万星,并不是因为它功能多花哨,而是它的工程实现方式,恰好踩准了自托管自动化工具的所有关键点。我前后花了几个晚上把它的源码结构、工作流引擎和部署方案完整过了一遍,又实际部署跑了一轮,这篇文章就… · 2026/11/4 9:27:32
Agent判断器选型:Laya与Jev对比及部署实战 前阵子有个Agent项目卡了我整整三天,问题不是模型能力不行,而是它太“自作主张”了。明明只让它查天气,它非要先调用日历再翻邮件,最后还要给自己写个总结;让它循环处理十批数据,跑到第五批突然开始乱改输入… · 2026/11/4 9:26:29
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38