ARTICLE DETAIL

资讯详情

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

JSON for Modern C++ 深入解析:adl_serializer 如何基于 ADL 实现用户自定义类型的默认序列化

JSON for Modern C++ 深入解析:adl_serializer 如何基于 ADL 实现用户自定义类型的默认序列化 JSON for Modern C 深入解析adl_serializer 如何基于 ADL 实现用户自定义类型的默认序列化【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json本文以nlohmann::adl_serializer的 API 文档为核心系统讲解 JSON for Modern C 库中默认序列化器的设计原理与用法它如何通过参数依赖查找Argument-Dependent LookupADL在用户类型的命名空间中定位to_json/from_json函数从而把任意用户自定义类型UDT转换为 JSON 值或从 JSON 值还原。读完本文你能够掌握默认序列化器的两个from_json重载的选择规则、to_json的触发路径并会编写默认可构造与非默认可构造类型两种典型场景的转换代码。adl_serializer 是什么在 JSON for Modern C 中basic_json类模板带有一个JSONSerializer模板参数用于决定“JSON 值与 C 值之间如何互相转换”的策略。该参数默认就是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的模板参数列表中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参数依赖查找来选择to_json/from_json函数的序列化器——即从待转换类型所在命名空间中查找对应的to_json/from_json函数。其设计可简化理解为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内部这个策略被具象为类型别名见 json.hpptemplatetypename T, typename SFINAE using json_serializer JSONSerializerT, SFINAE;所有“JSON 值 → C 值”与“C 值 → JSON 值”的转换最终都经由json_serializer分发而默认情形下它就是adl_serializer。源码级实现三个成员函数adl_serializer的完整实现位于 adl_serializer.hpp其主体结构为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从源码结构看有三个值得注意的设计点两个模板参数均“被忽略”。结构体签名是templatetypename ValueType, typename第二个模板参数来自basic_json::json_serializerT, SFINAE的 SFINAE 槽位实现中并不使用ValueType也只作为默认实参传递给成员模板的TargetType。这与 json_fwd.hpp 注释中 “This serializer ignores the template arguments” 的说法一致。成员函数本质是转发器。adl_serializer本身不含任何转换逻辑它只是以全限定形式::nlohmann::from_json(...)/::nlohmann::to_json(...)发起调用。由于调用表达式中出现了TargetType即用户类型编译器在 ADL 时会额外搜索该类型的命名空间从而“顺带”发现用户自行编写的自由函数to_json/from_json——这正是 ADL 机制发挥作用的所在。SFINAE 与noexcept双重透传。成员函数用- decltype(...)控制自身是否参与重载决议若库内不存在对应的::nlohmann::from_json重载则该成员被剔除出候选集同时用noexcept(noexcept(...))将底层函数的异常传播属性原样透出保证上层get()等 API 的noexcept标注准确。其中第二个from_json重载出现的detail::identity_tagTargetType是一个空标记类型定义见 identity_tag.hpptemplate class T struct identity_tag {};它携带类型信息但本身不带数据用于让编译器“探测”是否存在形如T from_json(const basic_json)的返回值风格重载详见下文“非默认可构造类型”一节。from_json 成员函数两个重载与选择规则from_json的完整签名如源码所示文档版见 from_json.md按用途分为两组// (1) 用于默认可构造的类型结果写入输出参数 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) 用于非默认可构造的类型直接返回值 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 {}))选择规则与官方文档一致重载 (1)在目标类型默认可构造时被选中重载 (2)在目标类型不可默认构造时被选中。参数与返回值约定参数方向含义jin待读取的 JSON 值valout写入转换结果的目标值仅重载 (1)返回值重载 (1) 无返回值结果写入val重载 (2) 返回j转换后的TargetType值。这个函数通常由basic_json的get()函数显式调用或经由转换运算符隐式触发调用。basic_json内部确实提供了两档实现见 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; }以及面向非默认可构造类型的特化分支见 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 成员函数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())参数约定与文档一致jout待写入的 JSON 值valin待读取的 C 值。该函数通常由basic_json的构造函数调用。从源码看构造路径正是如此见 json.hppJSONSerializerU::to_json(std::declvalbasic_json_t(), ...); // SFINAE 探测 JSONSerializerU::to_json(*this, std::forwardCompatibleType(val)); // 实际构造时调用即当你执行json j myStruct;时构造函数经JSONSerializerU::to_json分发到adl_serializer::to_json最终通过 ADL 找到用户命名空间中的自由函数to_json。是否存在可用的to_json同样由特征检测把关见 type_traits.hpp 的has_to_json。实战示例一默认可构造类型ADL 自由函数官方示例 from_json__default_constructible.cpp 演示了如何为用户类型实现from_json——当调用getns::person()时adl_serializer会通过 ADL 找到该函数#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结构体定义在namespace ns中from_json也必须定义在同一命名空间。调用链为j.getns::person()→get_impl(priority_tag0)person默认可构造且has_from_json为真→adl_serializerns::person::from_json(j, ret)→::nlohmann::from_json(j, ret)。最后一个表达式中出现了ns::personADL 会同时搜索namespace ns于是用户定义的ns::from_json被优先匹配。实战示例二非默认可构造类型特化 adl_serializer当类型不可默认构造时无法使用“先构造、再填充”的 (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不可默认构造于是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)被选中执行return JSONSerializerValueType::from_json(*this);见 json.hpp。这也解释了adl_serializer第二个from_json重载中identity_tagTargetType的作用库通过探测from_json(j, identity_tagTargetType{})是否可调用判断目标类型是否具备返回值风格的from_json。示例中注释特别强调了一个易踩的坑对某类型完全特化adl_serializer之后原模板中用于 ADL 分发的to_json一并被替换——因此必须同时手工提供to_json否则该类型将无法再转换为 JSON。实战示例三to_json 与隐式构造to_json.cpp 演示了序列化方向的用法。当你执行构造函数basic_json(ns::person)即json j p;时adl_serializer会调用用户命名空间中的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}注意输出键按字典序排序这是默认对象容器std::map的行为若需要保持插入顺序可换用 ordered_json即basic_jsonnlohmann::ordered_map其JSONSerializer模板参数同样默认为adl_serializer用法完全一致。to_json一侧的库内支持函数集中在 to_json.hppfrom_json一侧的内建重载std::nullptr_t、std::optional、算术类型、字符串、容器等集中在 from_json.hpp。用户自定义类型的自由函数与这些内建重载共同构成 ADL 的候选集由于自由函数位于用户类型所在命名空间对相应类型它总是优先于nlohmann命名空间内的同名重载被选中。版本历史与延伸阅读adl_serializer自2.1.0版本引入与from_json、to_json成员函数的版本标注一致。相关测试用例集中在 unit-udt.cpp用户自定义类型转换与 unit-conversions.cpp 等文件中可进一步验证 ADL 转换行为。成员函数完整文档from_json、to_json。小结adl_serializer本身逻辑极简——两个from_json重载加一个to_json全部只是对::nlohmann::from_json/::nlohmann::to_json的 SFINAE 转发它真正的价值在于作为basic_json的默认JSONSerializer借助 ADL 把序列化职责下放给用户类型所在的命名空间。理解“默认可构造走输出参数重载、非默认可构造走返回值重载”这一选择规则以及完全特化时必须补齐to_json的坑即可覆盖绝大多数 UDT ↔ JSON 转换场景。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表