因为C++枚举无内置字符串序列化能力,std::ostream无法直接输出其语义名称,JSON需键值对格式的字符串而非原始整数值。

为什么不能直接用 std::ostream 输出枚举到 JSON
因为 C++ 枚举(enum 或 enum class)本身没有内置的字符串序列化能力,std::cout 只会输出底层整数值,而非可读名。JSON 标准要求字符串字段必须是双引号包裹的文本,所以直接写 <code>{ "status": 2 } 不符合语义预期——你真正想要的是 { "status": "success" }。
常见错误现象:用 nlohmann::json 直接构造 json j = my_enum;,结果得到数字;或手动写 switch 但漏掉新添加的枚举项,导致运行时未定义行为。
- 枚举值增减后,所有
switch/if-else映射逻辑必须同步更新 -
enum class默认不支持隐式转int,强制static_cast容易掩盖类型安全意图 - 若用宏或 X-Macro 模拟反射,需确保头文件包含顺序和预处理时机一致,否则编译失败无明确提示
用 nlohmann::json 的自定义 to_json 重载最稳妥
这是目前主流、零依赖、类型安全的做法。关键不是“怎么写函数”,而是“在哪声明、作用域是否可见”。必须在枚举定义所在的命名空间内声明 to_json,否则 ADL(参数依赖查找)找不到它,JSON 库会退化为输出整数。
示例(正确位置):
namespace app {
enum class Status { idle, running, error };
<p>void to_json(nlohmann::json& j, const Status& s) {
switch (s) {
case Status::idle: j = "idle"; break;
case Status::running: j = "running"; break;
case Status::error: j = "error"; break;
default: j = "unknown";
}
}
} // namespace app
</p>
- 函数签名必须严格为
void to_json(nlohmann::json& j, const T&),且T是你要序列化的枚举类型 - 不要在全局命名空间或匿名命名空间里定义,否则 ADL 失效
- 如果枚举在类内部(如
class A { enum class E {}; };),to_json必须定义在A所在的外层命名空间,不能塞进A里(C++ 不允许类内定义非成员函数)
避免手写 switch:用数组查表 + static_assert 校验
当枚举值连续、从 0 开始(如 enum class Op { add, sub, mul };),可用 static_cast<size_t>(e)</size_t> 当下标查 const char* 数组,比 switch 更快,也更容易加编译期检查。
示例:
enum class Op { add, sub, mul };
inline constexpr std::array<const char> op_names = {"add", "sub", "mul"};
<p>void to_json(nlohmann::json& j, const Op& o) {
static_assert(op_names.size() == 3, "op_names size mismatch");
j = op_names[static_cast<size_t>(o)];
}
</size_t></p></const>
- 必须配合
static_assert,否则新增枚举项但忘了扩数组,运行时越界访问(UB) - 不适用于有显式赋值或非连续值的枚举,例如
enum E { a = 10, b = 20 };—— 这种查表会浪费大量空间且易错 - 数组元素必须是
const char*或std::string_view,不能用std::string(避免构造开销)
别碰宏生成代码,除非你控制整个构建链
网上流行的 X-Macro 或 Boost.PFR 方案看似“自动”,但实际引入了隐性耦合:X-Macro 要求所有枚举定义统一格式并额外维护宏列表;Boost.PFR 要求枚举是 trivial 且无 constexpr 构造函数,在 C++17 下对 enum class 支持也不稳定。
真实踩坑点:
- 用 CMake 的
configure_file生成头文件,但 IDE(如 VS Code)无法索引生成的符号,跳转/补全失效 - Clang 和 GCC 对同一段宏展开的诊断信息不一致,某处报错在另一编译器静默通过
- CI 环境中忘记清理生成的中间头文件,导致旧映射逻辑残留,JSON 输出突然变数字
真正省事的方式,是接受“每个枚举配一个 to_json 函数”这个事实——它只写一次,编译器帮你检查完整性,调试时能单步进去,出问题时错误位置清晰。所谓“自动”,往往是以可维护性换开发速度。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











