应避免直接用+拼接json字符串,需对双引号、反斜杠及控制字符(如 、 等)进行转义;推荐使用成熟json库(如nlohmann::json),手工转义仅适用于结构固定、低频生成且无外部依赖的场景。

用 std::string 拼接 JSON 字符串时,引号和转义怎么处理
直接用 + 拼接字符串生成 JSON 最容易在双引号、反斜杠、换行符上翻车。比如把 "name": "Alice
Bob" 写成裸字符串,结果 JSON 解析器报 Invalid string: control character in string。
关键不是“能不能拼”,而是“要不要自己写转义逻辑”。简单场景下可以手写一个轻量转义函数:
std::string json_escape(const std::string& s) {
std::string out;
out.reserve(s.size() * 2);
for (char c : s) {
switch (c) {
case '"': out += "\""; break;
case '\': out += "\\"; break;
case '': out += "\b"; break;
case '': out += "\f"; break;
case '
': out += "\n"; break;
case '
': out += "\r"; break;
case ' ': out += "\t"; break;
default:
if (c >= 0 && c < 32) {
out += "\u" + std::string(4 - std::to_string((int)c).length(), '0') +
std::to_string((int)c);
} else {
out += c;
}
}
}
return out;
}注意:这个实现只处理 ASCII 控制字符,不处理 UTF-8 多字节——如果你的字符串确定是 ASCII 或已知 UTF-8 安全(如来自 std::filesystem::path 的窄字符路径),可省略 Unicode 转义;否则需用 ICU 或 utf8cpp 解码后逐码点判断。
递归序列化 struct 时,如何避免手动写每个字段的 key 名
C++17 没有反射,所以没法像 Python 的 __dict__ 那样自动遍历成员。常见做法是显式声明序列化协议,但别硬编码字段名字符串——容易拼错且难维护。
立即学习“C++免费学习笔记(深入)”;
推荐用宏或结构体绑定方式统一管理:
- 对每个需要 JSON 化的 struct,定义一个
to_json()成员函数,内部调用辅助函数json::obj({{"name", name}, {"age", age}}) - 用宏自动生成键值对列表(例如
JSON_FIELDS(name, age, active)),再配合模板推导类型,避免字符串字面量和变量名不一致 - 如果用 C++20,可借助
std::tuple+std::apply和结构化绑定模拟“伪反射”,但要注意默认构造和 const 成员限制
别指望靠 auto 推导出字段名——编译器不提供符号名信息。所谓“零样板”JSON 序列化,在标准 C++ 里本质是权衡:要么接受宏,要么接受重复写 key 字符串。
嵌套对象和数组递归调用时,空值和 null 怎么区分
JSON 标准里 null 是一个独立类型,而 C++ 没有原生 nullable 值语义。你得决定:std::optional<int></int> 为空时输出 null,还是跳过字段?std::shared_ptr<t></t> 为空时是否等价于 null?
实操建议:
- 统一用
std::optional表达可选字段,并在递归入口检查has_value():有值则继续序列化,无值则输出"null" - 对指针类型(如
T*),不建议直接序列化为null,因为裸指针生命周期难保证;改用std::optional<:reference_wrapper>></:reference_wrapper>或明确封装为json::value类型 - 数组中混入
null(如[1, null, "hi"])必须显式 push,不能靠“未初始化值”自动变成null——C++ 里未初始化的int是未定义行为,不是 JSONnull
最易被忽略的是:JSON 数组允许任意类型混合,但 C++ 容器(如 std::vector<int></int>)不允许。你需要一个能 hold 多种类型的容器(哪怕只是 std::vector<:string></:string> + 手动拼接),或者用 std::variant<int double std::string std::nullptr_t></int> 做类型擦除。
不用第三方库时,性能瓶颈通常卡在哪
纯手工拼接 JSON 最大的性能杀手不是递归本身,而是频繁的小字符串分配和拷贝。每次 out += "key": " + json_escape(val) + "," 都可能触发内存重分配。
优化方向很实际:
- 预先估算长度:用
std::string::reserve(),比如对象字段数 × 20 字节 + 值总长 × 1.5(考虑转义膨胀) - 避免中间
std::string临时对象:把json_escape()改成接受std::string& out参数的 append 版本 - 数字转字符串别用
std::to_string():它在某些 STL 实现里慢且分配堆内存;换成fmt::format_to(若允许轻量依赖),或手写整数/浮点数 to_chars 版本 - 递归深度大时(如树形结构 > 100 层),考虑用栈+循环替代递归,防止栈溢出——但多数配置类 JSON 深度不超过 10,这点常被高估
真正影响落地的是可读性与维护性:一段 30 行的 to_json() 函数比一个 3 行的 nlohmann::json j = {...}; 更难 debug,尤其当字段增减、嵌套变更时。手工方案只适合固定结构、低频生成、无外部依赖要求的场景。










