十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JSON for Modern C++:全面掌握在内存中创建 JSON 值的五种核心方式

JSON for Modern C++:全面掌握在内存中创建 JSON 值的五种核心方式 JSON for Modern C全面掌握在内存中创建 JSON 值的五种核心方式【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json本文基于 nlohmann/jsonJSON for Modern C官方文档 creating_values.md 编写系统讲解在内存中构建json对象的完整方法从原生 C 值直接转换、用花括号初始化列表简洁书写、通过operator[]增量搭建嵌套结构以及利用_json用户自定义字面量原位解析 JSON 文本。读完本文你将理解这些方式各自的适用场景、类型推断规则与易错歧义点空对象 vs 空数组、单元素花括号初始化并能在实际工程中写出可读、准确、无歧义的 JSON 构建代码。JSON 值有两种来源要么从 JSON 文本解析而来要么直接在内存中创建。本文聚焦后者——如何把 C 世界的数值、字符串、容器转换成json若想从文本或流解析参见 Parsing 相关章节。一、从 C 值直接构造一条赋值语句生成一个 JSON任何一个受支持的 C 类型都可以直接赋值给json或作为构造参数传入。这是最直观的创建方式json j_number 42; // number整数 json j_float 3.141; // number浮点 json j_string Hello; // string json j_boolean true; // boolean json j_null nullptr; // null json j_vector std::vectorint{1, 2, 3}; // array: [1, 2, 3]这段示例来自 creating_values.md 的 From C values 小节。其底层依赖basic_json的万能兼容构造函数templatetypename CompatibleType basic_json(CompatibleType val) noexcept(...);按 basic_json 构造函数文档 的说明该重载把所有定义了to_json()的类型接住并把参数val转发给对应的json_serializerU::to_json其中U uncvref_tCompatibleType。它支持的类型非常宽泛数组arrayarray_t以及std::vector、std::deque、std::list、std::forward_list、std::array、std::valarray、std::set、std::unordered_set、std::multiset、std::unordered_multiset等任意元素可转 JSON 的容器对象objectobject_t以及std::map、std::unordered_map、std::multimap、std::unordered_multimap等键类型兼容string_t、值类型可转 JSON 的关联容器字符串stringstring_t、字符串字面量及兼容的字符串容器数值numbernumber_integer_t、number_unsigned_t、number_float_t以及所有可转换的数值类型int、size_t、int64_t、float、double等布尔booleanboolean_t/bool二进制binarybinary_t/std::vectoruint8_t。注意由于 C 类型系统无法区分字符串字面量与二进制字符数组所有兼容const char*的类型都会被导向字符串构造函数——这是出于向后兼容的既有设计。C 各标量类型与 JSON 类型的对应关系总结见 conversions其中列出了可参与转换的完整类型清单与to_json/from_json定制机制。二、用花括号初始化列表一眼读懂数组与对象的写法当需要一次性写出结构化的对象或数组时最优雅的方式是花括号初始化列表// 一个数组 json array {1, 2, 3, 4}; // 一个对象由若干 key/value 对组成 json object { {pi, 3.141}, {happy, true}, {name, Niels}, {nothing, nullptr}, {list, {1, 0, 2}}, {object, {{currency, USD}, {value, 42.99}}} };外层括号内的每个元素既可以嵌套数组、对象也可以混合不同类型的值。仓库自带的示例 basic_json__list_init_t.cpp 完整展示了嵌套场景的实际输出对应 outputjson j_empty_init_list json({}); // {} json j_object { {one, 1}, {two, 2} }; // {one:1,two:2} json j_array {1, 2, 3, 4}; // [1,2,3,4] json j_nested_object { {one, {1}}, {two, {1, 2}} }; // {one:[1],two:[1,2]} json j_nested_array { {{1}, one}, {{1, 2}, two} }; // [[[1],one],[[1,2],two]]底层类型推断规则关键在于数组还是对象由列表内容动态决定。按 basic_json 构造函数文档重载 5与 creating_values.md 的说明库采用的判定规则为若列表为空构造空的 JSON对象{}——因为 C 的空花括号{}语义上最接近空对象若列表元素全是以字符串开头的二元素子列表即 key/value 对则构造对象每对的第一元素为键、第二元素为值其余一切情况构造数组。这样设计的理由C 没有描述映射类型的原生活法只能以二元组列表表示映射而 JSON 规定键必须是字符串因此规则 2 是判定对象最宽松的约束其余情况按数组解释是安全的兜底。规则 1 的代价是无法用空初始化列表表达空数组。三、歧义边界与显式工厂函数json::array/json::object正因为{}语法同时承担数组与对象两种身份某些场景会产生歧义。官方文档特别给出警告并建议使用显式工厂函数 json::array 与 json::object 强制指定目标类型json empty_array_explicit json::array(); // [] json empty_object_explicit json::object(); // {} // 想要只含一个对象的数组而不是含一个键值对的对象 json array_of_objects json::array({{key, value}}); // [{key:value}]json::array(initializer_list_t init {})该静态函数把传入的初始化列表原样包装为数组省略参数或传空列表即得空数组[]。仓库示例 array.cpp 覆盖了四种关键形态json j_no_init_list json::array(); // [] json j_empty_init_list json::array({}); // [] json j_nonempty json::array({1, 2, 3, 4}); // [1,2,3,4] json j_list_of_pairs json::array({ {one, 1}, {two, 2} }); // [{one:1},{two:2}]值得注意最后一行json::array接收的是元素为键值对的列表结果却是数组数组内每个元素才是对象这正是array()存在的核心价值——同样的初始化列表若直接交给普通花括号构造会被推断成对象。json::object(initializer_list_t init {})该函数强制按对象语义解析列表元素必须是二元组且每个二元组的首元素必须是字符串否则抛出type_error.301。按 object 文档 的说法object()主要是为对称性而存在——普通初始化列表构造已经能表达任何对象真正不可替代的只有array()处理的两类边界空数组、键值对数组。这些工厂函数本质上是把type_deduction置为false、manual_type指定为value_t::array或value_t::object的特化形式见 basic_json 构造函数 的参数说明。若强制对象但列表无法构成键值对构造函数会抛出type_error.301而同一列表若走自动推断则会退化为数组。四、最容易踩坑的歧义单元素花括号初始化与上面相关还有一个陷阱json j{value};这种单元素花括号初始化默认会把value包进一个单元素数组而且这一行为历史上甚至因编译器而异GCC 会包装旧版 Clang 不会自 Clang 20 起两者行为已一致。官方 FAQ 的 brace-initialization-yields-arrays 条目给出了典型对比json j1 hello; json j2{j1}; // j2 是 [hello]并不是 j1 的拷贝 json j3(j1); // j3 是 hello —— 圆括号才是拷贝原因在 json_brace_init_copy_semantics 宏文档 中有底层解释C 在花括号初始化时总是优先匹配initializer_list构造函数而不是拷贝/移动构造函数。该库默认值为0关闭保持既有行为。如果你希望花括号初始化的对象/数组内容符合直觉有三种处理方式显式创建单元素数组json j json::array({obj});这样无论何时都得到[obj]拷贝用圆括号json j3(j1);选择加入宏JSON_BRACE_INIT_COPY_SEMANTICS让单元素花括号初始化退化为拷贝/移动语义。注意该宏必须在#include nlohmann/json.hpp之前定义且在 include 之后定义无效#define JSON_BRACE_INIT_COPY_SEMANTICS 1 #include nlohmann/json.hpp五、增量构建用operator[]边访问边创建当 JSON 结构需要逐层搭建例如从配置数据逐项填充时可以借助operator[]的自动创建特性访问一个尚不存在的对象键或数组下标时库会按需在内存中即时创建对应元素含中间层。creating_values.md 给出的精炼示例json j; // 初始为 null j[answer][everything] 42; // 自动升级为对象并写入 {answer:{everything:42}} j[list] {1, 0, 2}; // 添加数组键 j[list].push_back(3); // 数组尾部追加变为 [1,0,2,3]第一行json j;默认调用无参/null 构造函数得到一个 JSONnull值随后j[answer]访问不存在的键operator[]便将其按值类型展开——先让j成为对象再让j[answer]成为下一层对象从而完成answer.everything 42的深层写入。整个过程可读性极强构建顺序与 JSON 结构天然一致。需要扩展元素、在中间位置插入时可配合 push_back、emplace以及insert、erase等修改函数继续拼装详见 modifying values。六、_json字面量把 JSON 文本写进代码、原位解析如果你希望代码里直接出现一段类 JSON 语法并让它在编译期字符串所在处就被解析成json值那么用户自定义字面量_json是最合适的选择。仓库文档 operator_literal_json.cpp 展示了一个可直接编译运行的完整示例#include iostream #include iomanip #include nlohmann/json.hpp using json nlohmann::json; using namespace nlohmann::literals; int main() { json j R( {hello: world, answer: 42} )_json; std::cout std::setw(2) j \n; }格式化输出结果为见 operator_literal_json.output{ answer: 42, hello: world }作用域与命名空间字面量操作符按标准做法放入命名空间库推荐用以下任一方式引入以便后续迁移到下一主版本using nlohmann::literals::operator _json; using namespace nlohmann::literals; using namespace nlohmann::json_literals; using namespace nlohmann::literals::json_literals; using namespace nlohmann;如需让字面量全局可用可了解宏JSON_USE_GLOBAL_UDLS。_json字面量自版本 1.0.0 提供3.11.0 移入nlohmann::literals::json_literals命名空间3.13.0 起新增char8_t*重载C20。对应实现与回归测试可见 unit-udl.cpp。关键区分解析 vs 字符串_json的本质是解析所以它与字符串构造函数的结果截然不同——这是文档明确强调、也最容易混淆的点auto a 42_json; // number42 json b json(42); // string4242_json调用的是operator_json(const char*, size_t)内部等价于对这段文本执行一次parse(s, sn)因此任何parse会抛出的解析错误如非法 JSON它同样会抛出而json(42)走的是字符串兼容构造函数生成的是一个值为42的 JSON 字符串。七、更多构造途径类型化空值、拷贝/移动、迭代器区间与批量副本creating_values.md在文末将读者引导至 basic_json 构造函数总文档那里完整列出全部 9 个构造函数重载。除前述内容外还有几个实用入口值得了解按类型创建默认空值——basic_json(value_t v)可指定类型并得到其空初值指定的 value 类型初始值nullnullbooleanfalsestringnumber0object{}array[]binary空数组该构造函数的后置状态可通过clear()恢复。批量副本——basic_json(size_type cnt, const basic_json val)生成含cnt个val副本的数组cnt为 0 时得到空数组。迭代器区间构造——basic_json(iterator first, iterator last)以[first, last)的内容构造对数组/对象类型语义类似std::vector/std::map的区间构造对基本类型要求first恰为begin()、last为end()否则抛invalid_iterator.204对null值调用会抛invalid_iterator.206。注意两个迭代器必须来自同一 JSON 值预条件在 assertions 有运行时断言约束。拷贝与移动——拷贝构造函数保证*this other移动构造函数窃取源资源并把源置为null。两者分别提供强异常安全与不抛异常保证。绝大多数构造函数的重载拷贝、移动、null、计数构造都是常数级或线性级复杂度初始化列表构造函数复杂度线性于列表长度。八、小结与选型建议综合全文在内存中创建 JSON 值时应按场景选择你的需求推荐方式单个标量 / 现有 C 容器直接转 JSON赋值或构造从 C 值转换常量对象/数组字面量花括号初始化列表空数组、键值对形态的数组json::array(...)强制某键值对形态按对象解析、明确语义json::object(...)数据结构未知、需逐层动态拼装默认构造 operator[]增量构建 push_back/emplace想在源码中直接内联一段 JSON 文本... _json字面量需要拷贝、移动或区间构造对应构造函数重载需要特别防范两类歧义空花括号得到对象而非数组用json::array()解决以及单元素花括号初始化会包装成数组用圆括号拷贝或JSON_BRACE_INIT_COPY_SEMANTICS解决。延伸阅读basic_json 构造函数总览全部 9 种构造方式的签名、语义、异常与复杂度json::array / json::object强制指定数组/对象类型operator_json_json字面量的完整签名与版本历史Converting values可参与转换的完整 C 类型清单Modifying valuespush_back、emplace、insert、erase等后续修改手段Parsing从 JSON 文本、流或迭代器区间解析得到值【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表