存中創(chuàng)建 JSON 值的五種核心方式)
JSON for Modern C全面掌握在內(nèi)存中創(chuàng)建 JSON 值的五種核心方式【免費下載鏈接】jsonJSON for Modern C項目地址: https://gitcode.com/GitHub_Trending/js/json本文基于 nlohmann/jsonJSON for Modern C官方文檔 creating_values.md 編寫系統(tǒng)講解在內(nèi)存中構(gòu)建json對象的完整方法從原生 C 值直接轉(zhuǎn)換、用花括號初始化列表簡潔書寫、通過operator[]增量搭建嵌套結(jié)構(gòu)以及利用_json用戶自定義字面量原位解析 JSON 文本。讀完本文你將理解這些方式各自的適用場景、類型推斷規(guī)則與易錯歧義點空對象 vs 空數(shù)組、單元素花括號初始化并能在實際工程中寫出可讀、準(zhǔn)確、無歧義的 JSON 構(gòu)建代碼。JSON 值有兩種來源要么從 JSON 文本解析而來要么直接在內(nèi)存中創(chuàng)建。本文聚焦后者——如何把 C 世界的數(shù)值、字符串、容器轉(zhuǎn)換成json若想從文本或流解析參見 Parsing 相關(guān)章節(jié)。一、從 C 值直接構(gòu)造一條賦值語句生成一個 JSON任何一個受支持的 C 類型都可以直接賦值給json或作為構(gòu)造參數(shù)傳入。這是最直觀的創(chuàng)建方式j(luò)son j_number 42; // number整數(shù) 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 小節(jié)。其底層依賴basic_json的萬能兼容構(gòu)造函數(shù)templatetypename CompatibleType basic_json(CompatibleType val) noexcept(...);按 basic_json 構(gòu)造函數(shù)文檔 的說明該重載把所有定義了to_json()的類型接住并把參數(shù)val轉(zhuǎn)發(fā)給對應(yīng)的json_serializerU::to_json其中U uncvref_tCompatibleType。它支持的類型非常寬泛數(shù)組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等任意元素可轉(zhuǎn) JSON 的容器對象objectobject_t以及std::map、std::unordered_map、std::multimap、std::unordered_multimap等鍵類型兼容string_t、值類型可轉(zhuǎn) JSON 的關(guān)聯(lián)容器字符串stringstring_t、字符串字面量及兼容的字符串容器數(shù)值numbernumber_integer_t、number_unsigned_t、number_float_t以及所有可轉(zhuǎn)換的數(shù)值類型int、size_t、int64_t、float、double等布爾booleanboolean_t/bool二進制binarybinary_t/std::vectoruint8_t。注意由于 C 類型系統(tǒng)無法區(qū)分字符串字面量與二進制字符數(shù)組所有兼容const char*的類型都會被導(dǎo)向字符串構(gòu)造函數(shù)——這是出于向后兼容的既有設(shè)計。C 各標(biāo)量類型與 JSON 類型的對應(yīng)關(guān)系總結(jié)見 conversions其中列出了可參與轉(zhuǎn)換的完整類型清單與to_json/from_json定制機制。二、用花括號初始化列表一眼讀懂?dāng)?shù)組與對象的寫法當(dāng)需要一次性寫出結(jié)構(gòu)化的對象或數(shù)組時最優(yōu)雅的方式是花括號初始化列表// 一個數(shù)組 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}}} };外層括號內(nèi)的每個元素既可以嵌套數(shù)組、對象也可以混合不同類型的值。倉庫自帶的示例 basic_json__list_init_t.cpp 完整展示了嵌套場景的實際輸出對應(yīng) 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]]底層類型推斷規(guī)則關(guān)鍵在于數(shù)組還是對象由列表內(nèi)容動態(tài)決定。按 basic_json 構(gòu)造函數(shù)文檔重載 5與 creating_values.md 的說明庫采用的判定規(guī)則為若列表為空構(gòu)造空的 JSON對象{}——因為 C 的空花括號{}語義上最接近空對象若列表元素全是以字符串開頭的二元素子列表即 key/value 對則構(gòu)造對象每對的第一元素為鍵、第二元素為值其余一切情況構(gòu)造數(shù)組。這樣設(shè)計的理由C 沒有描述映射類型的原生活法只能以二元組列表表示映射而 JSON 規(guī)定鍵必須是字符串因此規(guī)則 2 是判定對象最寬松的約束其余情況按數(shù)組解釋是安全的兜底。規(guī)則 1 的代價是無法用空初始化列表表達(dá)空數(shù)組。三、歧義邊界與顯式工廠函數(shù)json::array/json::object正因為{}語法同時承擔(dān)數(shù)組與對象兩種身份某些場景會產(chǎn)生歧義。官方文檔特別給出警告并建議使用顯式工廠函數(shù) json::array 與 json::object 強制指定目標(biāo)類型json empty_array_explicit json::array(); // [] json empty_object_explicit json::object(); // {} // 想要只含一個對象的數(shù)組而不是含一個鍵值對的對象 json array_of_objects json::array({{key, value}}); // [{key:value}]json::array(initializer_list_t init {})該靜態(tài)函數(shù)把傳入的初始化列表原樣包裝為數(shù)組省略參數(shù)或傳空列表即得空數(shù)組[]。倉庫示例 array.cpp 覆蓋了四種關(guān)鍵形態(tài)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接收的是元素為鍵值對的列表結(jié)果卻是數(shù)組數(shù)組內(nèi)每個元素才是對象這正是array()存在的核心價值——同樣的初始化列表若直接交給普通花括號構(gòu)造會被推斷成對象。json::object(initializer_list_t init {})該函數(shù)強制按對象語義解析列表元素必須是二元組且每個二元組的首元素必須是字符串否則拋出type_error.301。按 object 文檔 的說法object()主要是為對稱性而存在——普通初始化列表構(gòu)造已經(jīng)能表達(dá)任何對象真正不可替代的只有array()處理的兩類邊界空數(shù)組、鍵值對數(shù)組。這些工廠函數(shù)本質(zhì)上是把type_deduction置為false、manual_type指定為value_t::array或value_t::object的特化形式見 basic_json 構(gòu)造函數(shù) 的參數(shù)說明。若強制對象但列表無法構(gòu)成鍵值對構(gòu)造函數(shù)會拋出type_error.301而同一列表若走自動推斷則會退化為數(shù)組。四、最容易踩坑的歧義單元素花括號初始化與上面相關(guān)還有一個陷阱json j{value};這種單元素花括號初始化默認(rèn)會把value包進一個單元素數(shù)組而且這一行為歷史上甚至因編譯器而異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 在花括號初始化時總是優(yōu)先匹配initializer_list構(gòu)造函數(shù)而不是拷貝/移動構(gòu)造函數(shù)。該庫默認(rèn)值為0關(guān)閉保持既有行為。如果你希望花括號初始化的對象/數(shù)組內(nèi)容符合直覺有三種處理方式顯式創(chuàng)建單元素數(shù)組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五、增量構(gòu)建用operator[]邊訪問邊創(chuàng)建當(dāng) JSON 結(jié)構(gòu)需要逐層搭建例如從配置數(shù)據(jù)逐項填充時可以借助operator[]的自動創(chuàng)建特性訪問一個尚不存在的對象鍵或數(shù)組下標(biāo)時庫會按需在內(nèi)存中即時創(chuàng)建對應(yīng)元素含中間層。creating_values.md 給出的精煉示例json j; // 初始為 null j[answer][everything] 42; // 自動升級為對象并寫入 {answer:{everything:42}} j[list] {1, 0, 2}; // 添加數(shù)組鍵 j[list].push_back(3); // 數(shù)組尾部追加變?yōu)?[1,0,2,3]第一行json j;默認(rèn)調(diào)用無參/null 構(gòu)造函數(shù)得到一個 JSONnull值隨后j[answer]訪問不存在的鍵operator[]便將其按值類型展開——先讓j成為對象再讓j[answer]成為下一層對象從而完成answer.everything 42的深層寫入。整個過程可讀性極強構(gòu)建順序與 JSON 結(jié)構(gòu)天然一致。需要擴展元素、在中間位置插入時可配合 push_back、emplace以及insert、erase等修改函數(shù)繼續(xù)拼裝詳見 modifying values。六、_json字面量把 JSON 文本寫進代碼、原位解析如果你希望代碼里直接出現(xiàn)一段類 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; }格式化輸出結(jié)果為見 operator_literal_json.output{ answer: 42, hello: world }作用域與命名空間字面量操作符按標(biāo)準(zhǔn)做法放入命名空間庫推薦用以下任一方式引入以便后續(xù)遷移到下一主版本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。對應(yīng)實現(xiàn)與回歸測試可見 unit-udl.cpp。關(guān)鍵區(qū)分解析 vs 字符串_json的本質(zhì)是解析所以它與字符串構(gòu)造函數(shù)的結(jié)果截然不同——這是文檔明確強調(diào)、也最容易混淆的點auto a 42_json; // number42 json b json(42); // string4242_json調(diào)用的是operator_json(const char*, size_t)內(nèi)部等價于對這段文本執(zhí)行一次parse(s, sn)因此任何parse會拋出的解析錯誤如非法 JSON它同樣會拋出而json(42)走的是字符串兼容構(gòu)造函數(shù)生成的是一個值為42的 JSON 字符串。七、更多構(gòu)造途徑類型化空值、拷貝/移動、迭代器區(qū)間與批量副本creating_values.md在文末將讀者引導(dǎo)至 basic_json 構(gòu)造函數(shù)總文檔那里完整列出全部 9 個構(gòu)造函數(shù)重載。除前述內(nèi)容外還有幾個實用入口值得了解按類型創(chuàng)建默認(rèn)空值——basic_json(value_t v)可指定類型并得到其空初值指定的 value 類型初始值nullnullbooleanfalsestringnumber0object{}array[]binary空數(shù)組該構(gòu)造函數(shù)的后置狀態(tài)可通過clear()恢復(fù)。批量副本——basic_json(size_type cnt, const basic_json val)生成含cnt個val副本的數(shù)組cnt為 0 時得到空數(shù)組。迭代器區(qū)間構(gòu)造——basic_json(iterator first, iterator last)以[first, last)的內(nèi)容構(gòu)造對數(shù)組/對象類型語義類似std::vector/std::map的區(qū)間構(gòu)造對基本類型要求first恰為begin()、last為end()否則拋invalid_iterator.204對null值調(diào)用會拋invalid_iterator.206。注意兩個迭代器必須來自同一 JSON 值預(yù)條件在 assertions 有運行時斷言約束??截惻c移動——拷貝構(gòu)造函數(shù)保證*this other移動構(gòu)造函數(shù)竊取源資源并把源置為null。兩者分別提供強異常安全與不拋異常保證。絕大多數(shù)構(gòu)造函數(shù)的重載拷貝、移動、null、計數(shù)構(gòu)造都是常數(shù)級或線性級復(fù)雜度初始化列表構(gòu)造函數(shù)復(fù)雜度線性于列表長度。八、小結(jié)與選型建議綜合全文在內(nèi)存中創(chuàng)建 JSON 值時應(yīng)按場景選擇你的需求推薦方式單個標(biāo)量 / 現(xiàn)有 C 容器直接轉(zhuǎn) JSON賦值或構(gòu)造從 C 值轉(zhuǎn)換常量對象/數(shù)組字面量花括號初始化列表空數(shù)組、鍵值對形態(tài)的數(shù)組json::array(...)強制某鍵值對形態(tài)按對象解析、明確語義json::object(...)數(shù)據(jù)結(jié)構(gòu)未知、需逐層動態(tài)拼裝默認(rèn)構(gòu)造 operator[]增量構(gòu)建 push_back/emplace想在源碼中直接內(nèi)聯(lián)一段 JSON 文本... _json字面量需要拷貝、移動或區(qū)間構(gòu)造對應(yīng)構(gòu)造函數(shù)重載需要特別防范兩類歧義空花括號得到對象而非數(shù)組用json::array()解決以及單元素花括號初始化會包裝成數(shù)組用圓括號拷貝或JSON_BRACE_INIT_COPY_SEMANTICS解決。延伸閱讀basic_json 構(gòu)造函數(shù)總覽全部 9 種構(gòu)造方式的簽名、語義、異常與復(fù)雜度json::array / json::object強制指定數(shù)組/對象類型operator_json_json字面量的完整簽名與版本歷史Converting values可參與轉(zhuǎn)換的完整 C 類型清單Modifying valuespush_back、emplace、insert、erase等后續(xù)修改手段Parsing從 JSON 文本、流或迭代器區(qū)間解析得到值【免費下載鏈接】jsonJSON for Modern C項目地址: https://gitcode.com/GitHub_Trending/js/json創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考