用戶自定義類型的默認(rèn)序列化)
JSON for Modern C 深入解析adl_serializer 如何基于 ADL 實現(xiàn)用戶自定義類型的默認(rèn)序列化【免費下載鏈接】jsonJSON for Modern C項目地址: https://gitcode.com/GitHub_Trending/js/json本文以nlohmann::adl_serializer的 API 文檔為核心系統(tǒng)講解 JSON for Modern C 庫中默認(rèn)序列化器的設(shè)計原理與用法它如何通過參數(shù)依賴查找Argument-Dependent LookupADL在用戶類型的命名空間中定位to_json/from_json函數(shù)從而把任意用戶自定義類型UDT轉(zhuǎn)換為 JSON 值或從 JSON 值還原。讀完本文你能夠掌握默認(rèn)序列化器的兩個from_json重載的選擇規(guī)則、to_json的觸發(fā)路徑并會編寫默認(rèn)可構(gòu)造與非默認(rèn)可構(gòu)造類型兩種典型場景的轉(zhuǎn)換代碼。adl_serializer 是什么在 JSON for Modern C 中basic_json類模板帶有一個JSONSerializer模板參數(shù)用于決定“JSON 值與 C 值之間如何互相轉(zhuǎn)換”的策略。該參數(shù)默認(rèn)就是adl_serializer其聲明位于 json_fwd.hpp/*! brief default JSONSerializer template argument This serializer ignores the template arguments and uses ADL ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) for serialization. */ templatetypename T void, typename SFINAE void struct adl_serializer;隨后在basic_json的模板參數(shù)列表中JSONSerializer缺省為adl_serializer見 json_fwd.hpptemplatetemplatetypename U, typename V, typename... Args class ObjectType std::map, templatetypename U, typename... Args class ArrayType std::vector, class StringType std::string, class BooleanType bool, class NumberIntegerType std::int64_t, class NumberUnsignedType std::uint64_t, class NumberFloatType double, templatetypename U class AllocatorType std::allocator, templatetypename T, typename SFINAE void class JSONSerializer adl_serializer, class BinaryType std::vectorstd::uint8_t, // cppcheck-suppress syntaxError class CustomBaseClass void class basic_json;因此文檔給出的抽象定義為templatetypename, typename struct adl_serializer;它是一個使用 ADLArgument-Dependent Lookup參數(shù)依賴查找來選擇to_json/from_json函數(shù)的序列化器——即從待轉(zhuǎn)換類型所在命名空間中查找對應(yīng)的to_json/from_json函數(shù)。其設(shè)計可簡化理解為templatetypename ValueType struct adl_serializer { templatetypename BasicJsonType static void to_json(BasicJsonType j, const T value) { // calls the to_json method in Ts namespace } templatetypename BasicJsonType static void from_json(const BasicJsonType j, T value) { // same thing, but with the from_json method } };在basic_json內(nèi)部這個策略被具象為類型別名見 json.hpptemplatetypename T, typename SFINAE using json_serializer JSONSerializerT, SFINAE;所有“JSON 值 → C 值”與“C 值 → JSON 值”的轉(zhuǎn)換最終都經(jīng)由json_serializer分發(fā)而默認(rèn)情形下它就是adl_serializer。源碼級實現(xiàn)三個成員函數(shù)adl_serializer的完整實現(xiàn)位于 adl_serializer.hpp其主體結(jié)構(gòu)為NLOHMANN_JSON_NAMESPACE_BEGIN /// sa https://json.nlohmann.me/api/adl_serializer/ templatetypename ValueType, typename struct adl_serializer { /// brief convert a JSON value to any value type templatetypename BasicJsonType, typename TargetType ValueType static auto from_json(BasicJsonType j, TargetType val) noexcept( noexcept(::nlohmann::from_json(std::forwardBasicJsonType(j), val))) - decltype(::nlohmann::from_json(std::forwardBasicJsonType(j), val), void()) { ::nlohmann::from_json(std::forwardBasicJsonType(j), val); } /// brief convert a JSON value to any value type templatetypename BasicJsonType, typename TargetType ValueType static auto from_json(BasicJsonType j) noexcept( noexcept(::nlohmann::from_json(std::forwardBasicJsonType(j), detail::identity_tagTargetType {}))) - decltype(::nlohmann::from_json(std::forwardBasicJsonType(j), detail::identity_tagTargetType {})) { return ::nlohmann::from_json(std::forwardBasicJsonType(j), detail::identity_tagTargetType {}); } /// brief convert any value type to a JSON value templatetypename BasicJsonType, typename TargetType ValueType static auto to_json(BasicJsonType j, TargetType val) noexcept( noexcept(::nlohmann::to_json(j, std::forwardTargetType(val)))) - decltype(::nlohmann::to_json(j, std::forwardTargetType(val)), void()) { ::nlohmann::to_json(j, std::forwardTargetType(val)); } }; NLOHMANN_JSON_NAMESPACE_END從源碼結(jié)構(gòu)看有三個值得注意的設(shè)計點兩個模板參數(shù)均“被忽略”。結(jié)構(gòu)體簽名是templatetypename ValueType, typename第二個模板參數(shù)來自basic_json::json_serializerT, SFINAE的 SFINAE 槽位實現(xiàn)中并不使用ValueType也只作為默認(rèn)實參傳遞給成員模板的TargetType。這與 json_fwd.hpp 注釋中 “This serializer ignores the template arguments” 的說法一致。成員函數(shù)本質(zhì)是轉(zhuǎn)發(fā)器。adl_serializer本身不含任何轉(zhuǎn)換邏輯它只是以全限定形式::nlohmann::from_json(...)/::nlohmann::to_json(...)發(fā)起調(diào)用。由于調(diào)用表達(dá)式中出現(xiàn)了TargetType即用戶類型編譯器在 ADL 時會額外搜索該類型的命名空間從而“順帶”發(fā)現(xiàn)用戶自行編寫的自由函數(shù)to_json/from_json——這正是 ADL 機(jī)制發(fā)揮作用的所在。SFINAE 與noexcept雙重透傳。成員函數(shù)用- decltype(...)控制自身是否參與重載決議若庫內(nèi)不存在對應(yīng)的::nlohmann::from_json重載則該成員被剔除出候選集同時用noexcept(noexcept(...))將底層函數(shù)的異常傳播屬性原樣透出保證上層get()等 API 的noexcept標(biāo)注準(zhǔn)確。其中第二個from_json重載出現(xiàn)的detail::identity_tagTargetType是一個空標(biāo)記類型定義見 identity_tag.hpptemplate class T struct identity_tag {};它攜帶類型信息但本身不帶數(shù)據(jù)用于讓編譯器“探測”是否存在形如T from_json(const basic_json)的返回值風(fēng)格重載詳見下文“非默認(rèn)可構(gòu)造類型”一節(jié)。from_json 成員函數(shù)兩個重載與選擇規(guī)則from_json的完整簽名如源碼所示文檔版見 from_json.md按用途分為兩組// (1) 用于默認(rèn)可構(gòu)造的類型結(jié)果寫入輸出參數(shù) val templatetypename BasicJsonType, typename TargetType ValueType static auto from_json(BasicJsonType j, TargetType val) noexcept( noexcept(::nlohmann::from_json(std::forwardBasicJsonType(j), val))) - decltype(::nlohmann::from_json(std::forwardBasicJsonType(j), val), void()) // (2) 用于非默認(rèn)可構(gòu)造的類型直接返回值 templatetypename BasicJsonType, typename TargetType ValueType static auto from_json(BasicJsonType j) noexcept( noexcept(::nlohmann::from_json(std::forwardBasicJsonType(j), detail::identity_tagTargetType {}))) - decltype(::nlohmann::from_json(std::forwardBasicJsonType(j), detail::identity_tagTargetType {}))選擇規(guī)則與官方文檔一致重載 (1)在目標(biāo)類型默認(rèn)可構(gòu)造時被選中重載 (2)在目標(biāo)類型不可默認(rèn)構(gòu)造時被選中。參數(shù)與返回值約定參數(shù)方向含義jin待讀取的 JSON 值valout寫入轉(zhuǎn)換結(jié)果的目標(biāo)值僅重載 (1)返回值重載 (1) 無返回值結(jié)果寫入val重載 (2) 返回j轉(zhuǎn)換后的TargetType值。這個函數(shù)通常由basic_json的get()函數(shù)顯式調(diào)用或經(jīng)由轉(zhuǎn)換運(yùn)算符隱式觸發(fā)調(diào)用。basic_json內(nèi)部確實提供了兩檔實現(xiàn)見 json.hpp 的get_impltemplate typename ValueType, detail::enable_if_t detail::is_default_constructibleValueType::value detail::has_from_jsonbasic_json_t, ValueType::value, int 0 ValueType get_impl(detail::priority_tag0 /*unused*/) const noexcept(noexcept( JSONSerializerValueType::from_json(std::declvalconst basic_json_t(), std::declvalValueType()))) { auto ret ValueType(); JSONSerializerValueType::from_json(*this, ret); return ret; }以及面向非默認(rèn)可構(gòu)造類型的特化分支見 json.hpptemplate typename ValueType, detail::enable_if_t detail::has_non_default_from_jsonbasic_json_t, ValueType::value, int 0 ValueType get_impl(detail::priority_tag1 /*unused*/) const noexcept(noexcept( JSONSerializerValueType::from_json(std::declvalconst basic_json_t()))) { return JSONSerializerValueType::from_json(*this); }兩檔的 SFINAE 條件分別由 type_traits.hpp 中的特征檢測提供has_from_json第 118 行起檢測JSONSerializerT::from_json(json const, udt)是否存在has_non_default_from_json第 142 行起檢測JSONSerializerT::from_json(json const)返回T是否存在注釋明確說明 “this overload is used for non-default-constructible user-defined-types”。to_json 成員函數(shù)templatetypename BasicJsonType, typename TargetType ValueType static auto to_json(BasicJsonType j, TargetType val) noexcept( noexcept(::nlohmann::to_json(j, std::forwardTargetType(val)))) - decltype(::nlohmann::to_json(j, std::forwardTargetType(val)), void())參數(shù)約定與文檔一致jout待寫入的 JSON 值valin待讀取的 C 值。該函數(shù)通常由basic_json的構(gòu)造函數(shù)調(diào)用。從源碼看構(gòu)造路徑正是如此見 json.hppJSONSerializerU::to_json(std::declvalbasic_json_t(), ...); // SFINAE 探測 JSONSerializerU::to_json(*this, std::forwardCompatibleType(val)); // 實際構(gòu)造時調(diào)用即當(dāng)你執(zhí)行json j myStruct;時構(gòu)造函數(shù)經(jīng)JSONSerializerU::to_json分發(fā)到adl_serializer::to_json最終通過 ADL 找到用戶命名空間中的自由函數(shù)to_json。是否存在可用的to_json同樣由特征檢測把關(guān)見 type_traits.hpp 的has_to_json。實戰(zhàn)示例一默認(rèn)可構(gòu)造類型ADL 自由函數(shù)官方示例 from_json__default_constructible.cpp 演示了如何為用戶類型實現(xiàn)from_json——當(dāng)調(diào)用getns::person()時adl_serializer會通過 ADL 找到該函數(shù)#include iostream #include nlohmann/json.hpp using json nlohmann::json; namespace ns { // a simple struct to model a person struct person { std::string name; std::string address; int age; }; } // namespace ns namespace ns { void from_json(const json j, person p) { j.at(name).get_to(p.name); j.at(address).get_to(p.address); j.at(age).get_to(p.age); } } // namespace ns int main() { json j; j[name] Ned Flanders; j[address] 744 Evergreen Terrace; j[age] 60; auto p j.getns::person(); std::cout p.name ( p.age ) lives in p.address std::endl; }輸出見 from_json__default_constructible.outputNed Flanders (60) lives in 744 Evergreen Terrace這里的要點person結(jié)構(gòu)體定義在namespace ns中from_json也必須定義在同一命名空間。調(diào)用鏈為j.getns::person()→get_impl(priority_tag0)person默認(rèn)可構(gòu)造且has_from_json為真→adl_serializerns::person::from_json(j, ret)→::nlohmann::from_json(j, ret)。最后一個表達(dá)式中出現(xiàn)了ns::personADL 會同時搜索namespace ns于是用戶定義的ns::from_json被優(yōu)先匹配。實戰(zhàn)示例二非默認(rèn)可構(gòu)造類型特化 adl_serializer當(dāng)類型不可默認(rèn)構(gòu)造時無法使用“先構(gòu)造、再填充”的 (1) 重載。官方示例 from_json__non_default_constructible.cpp 演示了另一條路徑——直接特化adl_serializer提供返回T的from_json#include iostream #include nlohmann/json.hpp using json nlohmann::json; namespace ns { // a simple struct to model a person (not default constructible) struct person { person(std::string n, std::string a, int aa) : name(std::move(n)), address(std::move(a)), age(aa) {} std::string name; std::string address; int age; }; } // namespace ns namespace nlohmann { template struct adl_serializerns::person { static ns::person from_json(const json j) { return {j.at(name), j.at(address), j.at(age)}; } // Heres the catch! You must provide a to_json method! Otherwise, you // will not be able to convert person to json, since you fully // specialized adl_serializer on that type static void to_json(json j, ns::person p) { j[name] p.name; j[address] p.address; j[age] p.age; } }; } // namespace nlohmann int main() { json j; j[name] Ned Flanders; j[address] 744 Evergreen Terrace; j[age] 60; auto p j.getns::person(); std::cout p.name ( p.age ) lives in p.address std::endl; }輸出同樣為Ned Flanders (60) lives in 744 Evergreen Terrace從源碼角度解釋這條路徑為何可行person不可默認(rèn)構(gòu)造于是get_impl(priority_tag0)因is_default_constructible為假被 SFINAE 剔除adl_serializerns::person特化提供的T from_json(const json)使has_non_default_from_json成立get_impl(priority_tag1)被選中執(zhí)行return JSONSerializerValueType::from_json(*this);見 json.hpp。這也解釋了adl_serializer第二個from_json重載中identity_tagTargetType的作用庫通過探測from_json(j, identity_tagTargetType{})是否可調(diào)用判斷目標(biāo)類型是否具備返回值風(fēng)格的from_json。示例中注釋特別強(qiáng)調(diào)了一個易踩的坑對某類型完全特化adl_serializer之后原模板中用于 ADL 分發(fā)的to_json一并被替換——因此必須同時手工提供to_json否則該類型將無法再轉(zhuǎn)換為 JSON。實戰(zhàn)示例三to_json 與隱式構(gòu)造to_json.cpp 演示了序列化方向的用法。當(dāng)你執(zhí)行構(gòu)造函數(shù)basic_json(ns::person)即json j p;時adl_serializer會調(diào)用用戶命名空間中的to_json#include iostream #include nlohmann/json.hpp using json nlohmann::json; namespace ns { // a simple struct to model a person struct person { std::string name; std::string address; int age; }; } // namespace ns namespace ns { void to_json(json j, const person p) { j json{ {name, p.name}, {address, p.address}, {age, p.age} }; } } // namespace ns int main() { ns::person p {Ned Flanders, 744 Evergreen Terrace, 60}; json j p; std::cout j std::endl; }輸出見 to_json.output{address:744 Evergreen Terrace,age:60,name:Ned Flanders}注意輸出鍵按字典序排序這是默認(rèn)對象容器std::map的行為若需要保持插入順序可換用 ordered_json即basic_jsonnlohmann::ordered_map其JSONSerializer模板參數(shù)同樣默認(rèn)為adl_serializer用法完全一致。to_json一側(cè)的庫內(nèi)支持函數(shù)集中在 to_json.hppfrom_json一側(cè)的內(nèi)建重載std::nullptr_t、std::optional、算術(shù)類型、字符串、容器等集中在 from_json.hpp。用戶自定義類型的自由函數(shù)與這些內(nèi)建重載共同構(gòu)成 ADL 的候選集由于自由函數(shù)位于用戶類型所在命名空間對相應(yīng)類型它總是優(yōu)先于nlohmann命名空間內(nèi)的同名重載被選中。版本歷史與延伸閱讀adl_serializer自2.1.0版本引入與from_json、to_json成員函數(shù)的版本標(biāo)注一致。相關(guān)測試用例集中在 unit-udt.cpp用戶自定義類型轉(zhuǎn)換與 unit-conversions.cpp 等文件中可進(jìn)一步驗證 ADL 轉(zhuǎn)換行為。成員函數(shù)完整文檔from_json、to_json。小結(jié)adl_serializer本身邏輯極簡——兩個from_json重載加一個to_json全部只是對::nlohmann::from_json/::nlohmann::to_json的 SFINAE 轉(zhuǎn)發(fā)它真正的價值在于作為basic_json的默認(rèn)JSONSerializer借助 ADL 把序列化職責(zé)下放給用戶類型所在的命名空間。理解“默認(rèn)可構(gòu)造走輸出參數(shù)重載、非默認(rèn)可構(gòu)造走返回值重載”這一選擇規(guī)則以及完全特化時必須補(bǔ)齊to_json的坑即可覆蓋絕大多數(shù) UDT ? JSON 轉(zhuǎn)換場景。【免費下載鏈接】jsonJSON for Modern C項目地址: https://gitcode.com/GitHub_Trending/js/json創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考