
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),僅供參考