nlohmann::json 直接赋值嵌套结构体需结构体为聚合类型(无用户构造函数、无私有成员等);否则须在 nlohmann 命名空间特化 to_json/from_json;文件写入应使用 std::ios::binary 模式并检查状态。

用 nlohmann::json 直接赋值嵌套结构体(C++20 可行,但有前提)
现代 C++ 项目里,nlohmann::json 是最省心的选择——它支持结构体自动序列化,但不是无条件的。关键在于:结构体必须是 aggregatable(聚合类型),即满足「无用户定义构造函数、无私有/保护非静态成员、无基类、无虚函数」等条件。
常见错误现象:error: no matching function for call to 'to_json',往往是因为加了 private: 成员或写了空构造函数 MyStruct() = default; 却忘了加 public:。
实操建议:
- 所有字段声明前加
public:,哪怕只有数据成员 - 避免任何构造函数(包括
= default),除非你手动实现to_json和from_json - 嵌套结构体也需满足同样规则;
std::vector、std::map、std::string等标准容器可直接用
示例:
struct Point { public: double x, y; };
struct Rect { public: Point top_left; double width, height; };
// ✅ 合法:可直接 json j = rect;
手动注册 to_json / from_json(绕过聚合限制)
一旦结构体含私有成员、需要字段重命名、或要跳过某些字段(比如内部缓存),就必须显式特化 to_json。这是进阶场景下最常踩坑的地方:函数签名错一个字符(比如漏掉 const &),编译器就静默忽略你的特化,继续报老错误。
使用场景:字段名和 JSON key 不一致、含 std::unique_ptr、需运行时过滤字段、结构体继承自基类。
实操建议:
- 特化必须在
nlohmann命名空间内,且参数类型必须完全匹配(const T&) - 不要返回值,直接对
json& j赋值:j["x"] = t.x; - 嵌套结构体调用时用
j["child"] = t.child;,让递归生效 - 如果结构体有 const 成员,需用
const_cast或改用 getter 方法(不推荐暴力 cast)
示例:
namespace nlohmann {
void to_json(json& j, const Rect& r) {
j["top_x"] = r.top_left.x;
j["top_y"] = r.top_left.y;
j["w"] = r.width;
}
}
std::ofstream 写入 JSON 文件时的编码与换行控制
JSON 存盘看似简单,但容易忽略两件事:Windows 下默认写入 \r\n 导致 JSON 校验失败(部分解析器严格),以及中文字段乱码(没设 UTF-8)。这不是 nlohmann 的问题,而是流操作的细节。
性能影响:未关闭 std::ofstream 就退出程序,文件可能为空或截断;频繁小写入不加 std::ios::binary 可能触发额外换行转换。
实操建议:
- 打开文件时加
std::ios::binary模式,彻底禁用文本模式换行转换 - 写入前调用
j.dump(-1)(-1 表示不缩进)或j.dump(2)(2 空格缩进),避免默认紧凑格式难调试 - 中文无需额外转义——
nlohmann::json默认 UTF-8,只要源文件保存为 UTF-8 即可 - 务必检查
file.is_open()和file.good(),尤其在嵌入式或权限受限环境
示例:
std::ofstream file("config.json", std::ios::binary);
if (file.is_open()) {
file
<h3>处理 <code>std::optional</code>、<code>std::variant</code> 等 C++17+ 类型</h3>
<p>这些类型不会被 <code>nlohmann::json</code> 自动识别,直接用会触发编译错误或静默丢数据。特别是 <code>std::optional<int></int></code>,有人误以为 <code>nullptr</code> 对应 JSON <code>null</code>,其实默认行为是跳过该字段,而不是写 <code>null</code>。</p>
<p>兼容性影响:旧版 <code>nlohmann</code>(std::variant 支持不完整;C++17 前项目需自行实现或降级用 <code>boost::variant</code>。</p>
<p>实操建议:</p>
- 对
std::optional<t></t>,特化to_json判断t.has_value(),有值则j = *t,否则j = nullptr - 对
std::variant<a></a>,用std::visit分支处理每种类型,注意json构造函数需显式指定类型(如json(*std::get_if<a>(&t))</a>) - 避免把
std::any往 JSON 塞——没有通用序列化语义,必须明确知道运行时类型
示例(optional):
namespace nlohmann {
template<typename t>
void to_json(json& j, const std::optional<t>& opt) {
if (opt.has_value()) j = *opt;
else j = nullptr;
}
}</t></typename>
复杂点在于:嵌套越深,手动特化的代码量指数增长;而依赖聚合类型又限制了封装性。真正需要权衡的,从来不是“能不能转”,而是“要不要把结构体设计成可序列化的形状”。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











