JSON for Modern C++ 宏(Macros)完全配置指南:从运行时断言到类型序列化宏速查
【免费下载链接】jsonJSON for Modern C++项目地址: https://gitcode.com/GitHub_Trending/js/json
nlohmann/json(即 “JSON for Modern C++”)的很多行为并非只能靠 API 调用控制,而是可以在包含json.hpp头文件之前通过定义预处理器宏来整体定制。本文以官方 API 文档中的 Macros 索引页为骨架,系统梳理这些宏的用途、默认值、底层实现与典型使用场景:从控制运行时断言、异常开关与诊断信息,到强制指定 C++ 标准、管理内联命名空间,再到用宏一行生成类型的序列化代码。读完本文,你将能根据嵌入式、高安全性、ABI 隔离等不同需求,准确挑选并配置所需宏,同时理解这些配置在头文件中的真实作用位置。
使用前提:宏必须在包含头文件之前定义
这些宏的生效时机非常一致:在包含nlohmann/json.hpp(或single_include单头文件版本)之前用#define定义。核心实现在 include/nlohmann/detail/macro_scope.hpp 中:头文件会先检查#if !defined(...),仅当宏尚未定义时才补上默认值。例如 JSON_ASSERT 的默认定义就是通过这种 "可被外部覆盖" 的模式提供的:
// allow overriding assert #if !defined(JSON_ASSERT) #include <cassert> // assert #define JSON_ASSERT(x) assert(x) #endif因此,"先定义后包含" 是这些宏生效的唯一前提;宏在库内部使用,不会泄漏污染用户代码(多数在库头文件结束时被解除)。官方还把完整清单汇总在了 API Macros 总览与特性说明页,可对照查阅。按作用域可把全部宏划分为以下类别:
| 类别 | 涉及宏 |
|---|---|
| 运行时断言 | JSON_ASSERT(x) |
| 异常与诊断 | JSON_TRY_USER/JSON_CATCH_USER(e)/JSON_THROW_USER(e)、JSON_NOEXCEPTION、JSON_DIAGNOSTICS、JSON_DIAGNOSTIC_POSITIONS |
| 语言标准探测 | JSON_HAS_CPP_11/14/17/20(及JSON_HAS_CPP_23等)、JSON_HAS_FILESYSTEM、JSON_HAS_EXPERIMENTAL_FILESYSTEM、JSON_HAS_RANGES、JSON_HAS_STD_FORMAT、JSON_HAS_THREE_WAY_COMPARISON |
| 平台裁剪 | JSON_NO_IO、JSON_SKIP_UNSUPPORTED_COMPILER_CHECK、JSON_USE_GLOBAL_UDLS |
| 版本与命名空间 | JSON_SKIP_LIBRARY_VERSION_CHECK、NLOHMANN_JSON_VERSION_MAJOR/MINOR/PATCH、NLOHMANN_JSON_NAMESPACE、NLOHMANN_JSON_NAMESPACE_BEGIN/END、NLOHMANN_JSON_NAMESPACE_NO_VERSION |
| 类型转换行为 | JSON_BRACE_INIT_COPY_SEMANTICS、JSON_DISABLE_ENUM_SERIALIZATION、JSON_USE_IMPLICIT_CONVERSIONS |
| 比较行为 | JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON |
| 序列化代码生成 | NLOHMANN_JSON_SERIALIZE_ENUM(_STRICT)及NLOHMANN_DEFINE_(DERIVED_)TYPE_*全家桶 |
下文逐一展开。
运行时断言:JSON_ASSERT
#define JSON_ASSERT(x) /* value */JSON_ASSERT(x)控制库中所有运行时断言执行什么代码,参数x是一个标量类型表达式。默认定义为assert(x)(见 macro_scope.hpp),因此:
- 直接使用默认值时,库的断言遵循标准
assert语义,编译期定义NDEBUG即可关闭; - 库内部用大量断言守护不变量,例如在
const对象上用operator[]访问不存在的 key 会触发断言并终止程序,避免未定义行为。
完整参考见 JSON_ASSERT 文档与运行时断言特性页。
默认行为示例
对以下const对象访问缺失的 key 会触发标准断言并输出到 stderr:
#include <nlohmann/json.hpp> using json = nlohmann::json; int main() { const json j = {{"key", "value"}}; auto v = j["missing"]; }典型输出为:
Assertion failed: (m_value.object->find(key) != m_value.object->end()), function operator[], file json.hpp, line 2144.用户自定义断言行为
重新定义JSON_ASSERT即可替换报告方式,例如输出当前函数名后调用std::abort:
#include <cstdio> #include <cstdlib> #define JSON_ASSERT(x) if(!(x)){fprintf(stderr, "assertion error in %s\n", __FUNCTION__); std::abort();} #include <nlohmann/json.hpp> using json = nlohmann::json; int main() { const json j = {{"key", "value"}}; auto v = j["missing"]; }输出为:
assertion error in operator[]注意:如果把宏定义为"不调用
std::abort"的代码,断言失败后库可能处于未定义状态;该宏在库内部使用完即被解除,不会影响库外代码。此宏自 3.9.0 版本起提供。
异常控制族:捕获、抛出、关闭与诊断
JSON_TRY_USER / JSON_CATCH_USER / JSON_THROW_USER
库内部大量使用try/catch/throw,但从不直接书写标准关键字,而是统一经由三个可替换宏(详见 JSON_THROW_USER 系列文档):
#define JSON_TRY_USER #define JSON_CATCH_USER(exception) #define JSON_THROW_USER(exception)其中JSON_CATCH_USER与JSON_THROW_USER各接收一个"异常参数"。典型用途是把异常体系整体替换为自定义异常、或者在一个不允许抛异常但需要记录错误路径的框架中拦截异常。注意:捕获/抛出宏会同时改变库内basic_json的异常行为,替换后应保证错误状态仍能被外部感知。
JSON_NOEXCEPTION:整体关闭异常
#define JSON_NOEXCEPTION定义后,库内的try被替换为if (true)、catch被替换为if (false)、throw被替换为std::abort(),从而在完全不启用异常(例如部分嵌入式/裸机环境、-fno-exceptions编译)时也能通过编译。默认不定义该宏。
#define JSON_NOEXCEPTION 1 #include <nlohmann/json.hpp> // ...相同效果可直接使用编译器选项-fno-exceptions。已知限制:MSVC 在关闭异常时拿不到异常的what()字符串。该宏自 2.1.0 提供,详见 JSON_NOEXCEPTION 文档。
JSON_DIAGNOSTICS:开启扩展诊断信息
#define JSON_DIAGNOSTICS /* 1 开启 / 0 关闭(默认) */默认值为0,默认定义可在 abi_macros.hpp 中确认;未定义时库会自动补成默认值。设为1后,异常消息中会附带一个JSON Pointer,精确指出引发异常的值的位置。
例如没有诊断信息时,类型不匹配的异常难以定位;开启后消息形如:
[json.exception.type_error.302] type must be string, but is number并附带JSON Pointer如/address/housenumber,指明是哪个字段类型不对(官方示例见 diagnostics_extended.cpp 与 diagnostics_standard.cpp 的对比输出)。
需要权衡的是:开启JSON_DIAGNOSTICS会使每个 JSON 值对象增大一个指针并带来额外运行时开销。该宏也可由 CMake 选项JSON_Diagnostics(默认OFF)统一控制——但注意该 CMake 选项只在从源码构建库时生效。自 3.11.0 起,宏的值会被编码进命名空间,因此允许代码库不同编译单元使用不同取值而不会违反 ODR(One Definition Rule);即便如此仍建议全库一致取值以最大化互操作性。详见 JSON_DIAGNOSTICS 文档与 JSON_Diagnostic_Positions 相关 CMake 文档。
JSON_DIAGNOSTIC_POSITIONS:访问元素字节位置
开启JSON_DIAGNOSTIC_POSITIONS后,basic_json会新增 start_pos() 与 end_pos() 两个成员函数。若该值由parse产生,可用它们查询该值在输入字符串中的字节区间;异常也会利用这些位置来帮助定位解析错误(示例见 diagnostic_positions_exception.cpp)。默认关闭,同样可通过 CMake 选项JSON_Diagnostic_Positions(默认OFF)控制。详见 JSON_DIAGNOSTIC_POSITIONS 文档。
语言标准与特性探测覆盖族
库目标标准是 C++11,但对 C++17 之后的特性(std::string_view、std::filesystem、std::ranges、std::format、三路比较等)做了自动探测。当编译器对标准的实现不够完整、导致探测错误时,可用以下宏强制指定。
JSON_HAS_CPP_11 / 14 / 17 / 20(含 23/26)
通过#define JSON_HAS_CPP_17之类,可无条件假定某标准启用,覆盖库的内部检测。典型用途是修正只实现部分标准特性的编译器。参考 JSON_HAS_CPP_11 文档。
JSON_HAS_FILESYSTEM / JSON_HAS_EXPERIMENTAL_FILESYSTEM
C++17 下库提供与std::filesystem::path的互转。由于<filesystem>支持不均衡,库默认尝试探测:JSON_HAS_FILESYSTEM对应标准<filesystem>,JSON_HAS_EXPERIMENTAL_FILESYSTEM对应<experimental/filesystem>。如需强制指定其一,定义为1即可(见 JSON_HAS_FILESYSTEM 文档)。
JSON_HAS_RANGES / JSON_HAS_STD_FORMAT / JSON_HAS_THREE_WAY_COMPARISON
JSON_HAS_RANGES:控制是否启用std::ranges相关适配;JSON_HAS_STD_FORMAT:控制<format>/std::formatter格式化支持(对应std::format示例见 std_formatter.c++20.cpp);JSON_HAS_THREE_WAY_COMPARISON:控制<=>三路比较支持(见 operator_spaceship 示例)。
各自的完整用法见 JSON_HAS_RANGES、JSON_HAS_STD_FORMAT、JSON_HAS_THREE_WAY_COMPARISON 文档。
平台与安全裁剪
JSON_NO_IO:去除对 C++ I/O 头文件的依赖
定义后,库不再包含<cstdio>、<ios>、<iosfwd>、<istream>、<ostream>等头文件,并剔除依赖它们的parse(从流/文件解析)等函数。这对禁止 I/O 的高安全环境(如 Intel SGX 飞地)至关重要。参考 JSON_NO_IO 文档。
JSON_SKIP_UNSUPPORTED_COMPILER_CHECK:跳过编译器检测
默认情况下,库检测到已知不完整支持 C++11 的编译器会产生编译错误。定义本宏后该检查被跳过,可在"未完全支持 C++11 的编译器"上尝试编译,前提是不使用它不支持的库特性。参考 JSON_SKIP_UNSUPPORTED_COMPILER_CHECK 文档。
JSON_USE_GLOBAL_UDLS:用户定义字面量的命名空间位置
定义为1(默认)时,operator""_json与operator""_json_pointer被放入全局命名空间;否则位于nlohmann::literals::json_literals。当项目已有同名 UDL 冲突时,可将其置入受限命名空间。参考 JSON_USE_GLOBAL_UDLS 文档。
库版本与内联命名空间宏
JSON_SKIP_LIBRARY_VERSION_CHECK
若同一编译单元通过不同路径混入了多个版本的头文件,库默认会发出警告;定义本宏即可跳过版本一致性检查。参考 JSON_SKIP_LIBRARY_VERSION_CHECK 文档。
NLOHMANN_JSON_VERSION_MAJOR / MINOR / PATCH
这三个宏由库自行定义,内容符合 语义化版本 2.0.0 中确认。参考 NLOHMANN_JSON_VERSION_MAJOR 文档。
NLOHMANN_JSON_NAMESPACE / _BEGIN / _END / _NO_VERSION
从 3.11.0 起,nlohmann命名空间下还有一个随版本变化的内联 ABI 命名空间(默认结构见 namespace 结构文档)。相关宏用于:
#define NLOHMANN_JSON_NAMESPACE /* 值为 nlohmann 完整命名空间名(含内联 ABI 部分) */NLOHMANN_JSON_NAMESPACE:展开为命名空间全名,可用它替代直接写nlohmann,在代码中输出或做宏拼接;NLOHMANN_JSON_NAMESPACE_BEGIN/NLOHMANN_JSON_NAMESPACE_END:打开/关闭该命名空间,典型用于为第三方类型添加to_json/from_json特化时进入正确的命名空间;NLOHMANN_JSON_NAMESPACE_NO_VERSION:定义为1时去掉内联命名空间的版本分量。
后两者的默认定义形如:
namespace nlohmann { inline namespace json_abi_v3_12_0 {} // namespace json_abi_v3_12_0 } // namespace nlohmann命名空间宏的意义在于 ABI 隔离:当同一个二进制中混入不同配置(例如部分代码开启JSON_DIAGNOSTICS、部分未开启)时,宏取值被编码进命名空间,从而生成不同的符号名,避免 ODR 违规。相关示例与用法见 NLOHMANN_JSON_NAMESPACE 文档、NAMESPACE_BEGIN/END 文档、NAMESPACE_NO_VERSION 文档及命名空间特性页。
类型转换行为开关
JSON_USE_IMPLICIT_CONVERSIONS
默认隐式转换开启(1)。若希望所有取值必须显式调用get<T>(),可定义为0关闭,从而避免隐式operator ValueType引发的意外类型转换。参考 JSON_USE_IMPLICIT_CONVERSIONS 文档。
JSON_BRACE_INIT_COPY_SEMANTICS
定义为1时,json j{value};这种单元素花括号初始化被视为对value的拷贝/移动,而不是把 value 包进单元素数组(默认0,保持既有语义)。它修正的是 C++ 列表初始化带来的歧义。参考 JSON_BRACE_INIT_COPY_SEMANTICS 文档。
JSON_DISABLE_ENUM_SERIALIZATION
定义后,库不再为枚举类型自动生成默认的序列化/反序列化函数(默认把枚举当整数处理),必须由用户显式提供(例如配合NLOHMANN_JSON_SERIALIZE_ENUM)。参考 JSON_DISABLE_ENUM_SERIALIZATION 文档。
比较行为:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
解析回调产生的 "discarded" 值在比较语义上有历史遗留问题。该宏默认0:discarded 值不与自己相等;定义为1则恢复旧行为(discarded == discarded)。旧行为已被弃用。参考 JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 文档。
序列化/反序列化宏
这一族宏是索引页篇幅最大、也最常被实战使用的部分,全部围绕"用 JSON 对象字段名映射 C++ 成员名"展开。具体展开后的等价手写代码,官方在任意类型转换页 arbitrary_types.md 有详细讨论。
枚举:NLOHMANN_JSON_SERIALIZE_ENUM(_STRICT)
默认枚举会被序列化为整数;一旦枚举被重排或插入新值,旧 JSON 反序列化结果可能错位或未定义。该宏允许为每个枚举值指定稳定的 JSON 表示:
#define NLOHMANN_JSON_SERIALIZE_ENUM(type, conversion...)type为枚举类型名,conversion为(枚举值, JSON 值)的成对列表。宏会在枚举所在命名空间内生成如下两个函数:
template<typename BasicJsonType> inline void to_json(BasicJsonType& j, const type& e); template<typename BasicJsonType> inline void from_json(const BasicJsonType& j, type& e);底层实现见 macro_scope.hpp:它用static_assert校验类型确实是枚举,然后把转换表放进静态数组,to_json用std::find_if查找首个匹配的枚举值——因此列表靠前的转换优先。例如让Color::red序列化为"red",同时允许从"rot"反序列化回Color::red,就需把第二组映射放在后面作备选(完整示例见 nlohmann_json_serialize_enum_2.cpp)。
两个重要行为:
- 当
get<ENUM_TYPE>()遇到未定义的 JSON 值时,默认回落到第一个指定的转换——因此应把"兜底"映射放在首位; - 若同一枚举或 JSON 值出现在多个转换中,取从上往下第一个命中。
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT是严格变体:输入未在映射表中定义时抛异常,而不是回落到第一个映射。两者分别见 枚举序列化文档与严格变体文档,更多背景在枚举转换特性页。仓库测试用例位于 tests/src/unit-udt.cpp。
类的三大变体:INTRUSIVE / NON_INTRUSIVE / ONLY_SERIALIZE
对普通类(class/struct),库提供"侵入式"与"非侵入式"两类宏,每类又有三种语义变体:
// 1. 普通版:反序列化用 at(),缺 key 抛 out_of_range.403 // 2. WITH_DEFAULT:反序列化用 value(),缺 key 回落默认值 // 3. ONLY_SERIALIZE:只生成 to_json,不生成 from_json // —— 侵入式(宏写在类体内部,可访问 private 成员)—— #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...) #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...) #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(type, member...) // —— 非侵入式(宏写在类外、所在命名空间内,只能访问 public 成员)—— #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(type, member...) #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(type, member...) #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(type, member...)选择对照如下:
| 需要访问 private 成员 | 只需序列化 | 反序列化允许缺字段 | 推荐宏 |
|---|---|---|---|
| 是 | 否 | 否 | NLOHMANN_DEFINE_TYPE_INTRUSIVE |
| 是 | 否 | 是 | NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT |
| 是 | 是 | 不适用 | NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE |
| 否 | 否 | 否 | NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE |
| 否 | 否 | 是 | NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT |
| 否 | 是 | 不适用 | NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE |
要点说明(可对照侵入式/非侵入式两篇完整文档 intrusive / non-intrusive):
- 参数:第一个是类型名,其后是以逗号分隔的成员名列表;当前实现上限为 63 个成员,超出需手写
to_json/from_json; - 缺字段处理:普通版
from_json内部走at(),key 缺失抛out_of_range.403;WITH_DEFAULT版内部默认构造一个同类型对象,把其成员值作为默认值传给value()填充缺失字段(等价展开见 nlohmann_define_type_intrusive_with_default_explicit.cpp); ONLY_SERIALIZE版适用于无默认构造函数、只需输出 JSON 的类型;- 这两类宏只会生成对象式(键名)JSON,一成员一 key;若需要把成员按位置序列化成 JSON 数组,没有现成宏,须手写
to_json/from_json自行构造/解析json::array(); - 侵入式宏在类内生成
friend void to_json/from_json,因此能访问私有成员;非侵入式要求被列成员为 public; - 宏展开源码见 macro_scope.hpp 中 NLOHMANN_DEFINE_TYPE_INTRUSIVE 族,仓库内对应宏与等价手写示例文件位于 docs/mkdocs/docs/examples(如
nlohmann_define_type_intrusive_macro.cpp、nlohmann_define_type_non_intrusive_explicit.cpp等),并有unit-udt_macro.cpp等测试覆盖。
派生类与自定义字段名:DERIVED / WITH_NAMES 系列
针对继承场景与字段名定制,索引页还列出两组扩展:
- 派生类宏(nlohmann_define_derived_type.md):
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE、_WITH_DEFAULT、_ONLY_SERIALIZE及对应的_NON_INTRUSIVE三个变体,用于同时序列化基类与派生类成员; - 自定义字段名宏(nlohmann_define_type_with_names.md):
..._WITH_NAMES变体允许把 JSON key 映射为不同于 C++ 成员名的字符串,同样覆盖侵入式/非侵入式、派生类,以及_WITH_DEFAULT、_ONLY_SERIALIZE的组合,例如:
| 宏 | 语义 |
|---|---|
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES | 侵入式,key 用自定义名 |
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES | 侵入式 + 允许缺字段 + 自定义名 |
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES | 侵入式,仅序列化,自定义名 |
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(及_WITH_DEFAULT_/_ONLY_SERIALIZE_组合) | 非侵入式等价变体 |
NLOHMANN_DEFINE_DERIVED_TYPE_*_WITH_NAMES | 派生类等价变体 |
完整 18 个成员宏的清单、签名与历史版本(NLOHMANN_DEFINE_TYPE_*族 3.9.0 起引入,WITH_DEFAULT3.11.0、ONLY_SERIALIZE3.11.3 等)均可回到 API Macros 索引页逐项点击查阅。
选型建议小结
- 只想更安全:定义
JSON_ASSERT自定义断言日志,或JSON_USE_IMPLICIT_CONVERSIONS=0让取值全部显式化; - 不想要异常/I-O:
JSON_NOEXCEPTION(或-fno-exceptions)+JSON_NO_IO,适合安全飞地与资源受限平台; - 排错困难:开
JSON_DIAGNOSTICS(异常携带 JSON Pointer)与JSON_DIAGNOSTIC_POSITIONS(解析位置),接受体积/性能开销; - 编译器探测不准:按需定义
JSON_HAS_CPP_17、JSON_HAS_FILESYSTEM等强制覆盖; - 多版本/多配置共存:理解并善用
NLOHMANN_JSON_NAMESPACE_*的 ABI 内联命名空间机制; - 希望类型零样板接入 JSON:先按"是否需要 private、是否允许缺字段、是否只需写出"三个问题在对照表中选定基础变体,再按"是否派生类、是否需要别名 key"决定是否叠加
DERIVED与WITH_NAMES。
所有宏的官方逐项文档均可从 API Macros 总览出发继续深入,配套示例源码位于 docs/mkdocs/docs/examples,宏实现可研读 macro_scope.hpp 与 abi_macros.hpp。
【免费下载链接】jsonJSON for Modern C++项目地址: https://gitcode.com/GitHub_Trending/js/json
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考