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

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

2026-09-06 10:30:26作者:乔或婵

本文以 nlohmann::adl_serializer 的 API 文档为核心,系统讲解 JSON for Modern C++ 库中默认序列化器的设计原理与用法:它如何通过参数依赖查找(Argument-Dependent Lookup,ADL)在用户类型的命名空间中定位 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.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;

随后在 basic_json 的模板参数列表中,JSONSerializer 缺省为 adl_serializer(见 json_fwd.hpp):

template<template<typename U, typename V, typename... Args> class ObjectType =
         std::map,
         template<typename 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,
         template<typename U> class AllocatorType = std::allocator,
         template<typename T, typename SFINAE = void> class JSONSerializer =
         adl_serializer,
         class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
         class CustomBaseClass = void>
class basic_json;

因此文档给出的抽象定义为:

template<typename, typename>
struct adl_serializer;

它是一个使用 ADL(Argument-Dependent Lookup,参数依赖查找)来选择 to_json/from_json 函数的序列化器——即从待转换类型所在命名空间中查找对应的 to_json/from_json 函数。其设计可简化理解为:

template<typename ValueType>
struct adl_serializer {
    template<typename BasicJsonType>
    static void to_json(BasicJsonType& j, const T& value) {
        // calls the "to_json" method in T's namespace
    }

    template<typename BasicJsonType>
    static void from_json(const BasicJsonType& j, T& value) {
        // same thing, but with the "from_json" method
    }
};

basic_json 内部,这个策略被具象为类型别名(见 json.hpp):

template<typename T, typename SFINAE>
using json_serializer = JSONSerializer<T, 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/
template<typename ValueType, typename>
struct adl_serializer
{
    /// @brief convert a JSON value to any value type
    template<typename BasicJsonType, typename TargetType = ValueType>
    static auto from_json(BasicJsonType && j, TargetType& val) noexcept(
        noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))
    -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void())
    {
        ::nlohmann::from_json(std::forward<BasicJsonType>(j), val);
    }

    /// @brief convert a JSON value to any value type
    template<typename BasicJsonType, typename TargetType = ValueType>
    static auto from_json(BasicJsonType && j) noexcept(
    noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {})))
    -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {}))
    {
        return ::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {});
    }

    /// @brief convert any value type to a JSON value
    template<typename BasicJsonType, typename TargetType = ValueType>
    static auto to_json(BasicJsonType& j, TargetType && val) noexcept(
        noexcept(::nlohmann::to_json(j, std::forward<TargetType>(val))))
    -> decltype(::nlohmann::to_json(j, std::forward<TargetType>(val)), void())
    {
        ::nlohmann::to_json(j, std::forward<TargetType>(val));
    }
};

NLOHMANN_JSON_NAMESPACE_END

从源码结构看,有三个值得注意的设计点:

  1. 两个模板参数均“被忽略”。结构体签名是 template<typename ValueType, typename>:第二个模板参数来自 basic_json::json_serializer<T, SFINAE> 的 SFINAE 槽位,实现中并不使用;ValueType 也只作为默认实参传递给成员模板的 TargetType。这与 json_fwd.hpp 注释中 “This serializer ignores the template arguments” 的说法一致。
  2. 成员函数本质是转发器adl_serializer 本身不含任何转换逻辑,它只是以全限定形式 ::nlohmann::from_json(...)/::nlohmann::to_json(...) 发起调用。由于调用表达式中出现了 TargetType(即用户类型),编译器在 ADL 时会额外搜索该类型的命名空间,从而“顺带”发现用户自行编写的自由函数 to_json/from_json——这正是 ADL 机制发挥作用的所在。
  3. SFINAE 与 noexcept 双重透传。成员函数用 -> decltype(...) 控制自身是否参与重载决议(若库内不存在对应的 ::nlohmann::from_json 重载,则该成员被剔除出候选集),同时用 noexcept(noexcept(...)) 将底层函数的异常传播属性原样透出,保证上层 get() 等 API 的 noexcept 标注准确。

其中第二个 from_json 重载出现的 detail::identity_tag<TargetType> 是一个空标记类型,定义见 identity_tag.hpp

template <class T> struct identity_tag {};

它携带类型信息但本身不带数据,用于让编译器“探测”是否存在形如 T from_json(const basic_json&) 的返回值风格重载(详见下文“非默认可构造类型”一节)。

from_json 成员函数:两个重载与选择规则

from_json 的完整签名如源码所示(文档版见 from_json.md),按用途分为两组:

// (1) 用于默认可构造的类型:结果写入输出参数 val
template<typename BasicJsonType, typename TargetType = ValueType>
static auto from_json(BasicJsonType && j, TargetType& val) noexcept(
    noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))
-> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void())

// (2) 用于非默认可构造的类型:直接返回值
template<typename BasicJsonType, typename TargetType = ValueType>
static auto from_json(BasicJsonType && j) noexcept(
noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {})))
-> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {}))

选择规则(与官方文档一致):

  1. 重载 (1) 在目标类型默认可构造时被选中;
  2. 重载 (2) 在目标类型不可默认构造时被选中。

参数与返回值约定:

参数 方向 含义
j in 待读取的 JSON 值
val out 写入转换结果的目标值(仅重载 (1))

返回值:重载 (1) 无返回值(结果写入 val);重载 (2) 返回 j 转换后的 TargetType 值。

这个函数通常由 basic_jsonget() 函数(显式调用或经由转换运算符隐式触发)调用。basic_json 内部确实提供了两档实现,见 json.hppget_impl

template < typename ValueType,
           detail::enable_if_t <
               detail::is_default_constructible<ValueType>::value&&
               detail::has_from_json<basic_json_t, ValueType>::value,
               int > = 0 >
ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept(
            JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
{
    auto ret = ValueType();
    JSONSerializer<ValueType>::from_json(*this, ret);
    return ret;
}

以及面向非默认可构造类型的特化分支(见 json.hpp):

template < typename ValueType,
           detail::enable_if_t <
               detail::has_non_default_from_json<basic_json_t, ValueType>::value,
               int > = 0 >
ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept(
            JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>())))
{
    return JSONSerializer<ValueType>::from_json(*this);
}

两档的 SFINAE 条件分别由 type_traits.hpp 中的特征检测提供:

  • has_from_json(第 118 行起):检测 JSONSerializer<T>::from_json(json const&, udt&) 是否存在;
  • has_non_default_from_json(第 142 行起):检测 JSONSerializer<T>::from_json(json const&)(返回 T)是否存在,注释明确说明 “this overload is used for non-default-constructible user-defined-types”。

to_json 成员函数

template<typename BasicJsonType, typename TargetType = ValueType>
static auto to_json(BasicJsonType& j, TargetType && val) noexcept(
    noexcept(::nlohmann::to_json(j, std::forward<TargetType>(val))))
-> decltype(::nlohmann::to_json(j, std::forward<TargetType>(val)), void())

参数约定(与文档一致):

  • j(out):待写入的 JSON 值;
  • val(in):待读取的 C++ 值。

该函数通常由 basic_json 的构造函数调用。从源码看,构造路径正是如此(见 json.hpp):

JSONSerializer<U>::to_json(std::declval<basic_json_t&>(), ...);   // SFINAE 探测
JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val)); // 实际构造时调用

即当你执行 json j = myStruct; 时,构造函数经 JSONSerializer<U>::to_json 分发到 adl_serializer::to_json,最终通过 ADL 找到用户命名空间中的自由函数 to_json。是否存在可用的 to_json 同样由特征检测把关,见 type_traits.hpphas_to_json

实战示例一:默认可构造类型(ADL 自由函数)

官方示例 from_json__default_constructible.cpp 演示了如何为用户类型实现 from_json——当调用 get<ns::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.get<ns::person>();

    std::cout << p.name << " (" << p.age << ") lives in " << p.address << std::endl;
}

输出(见 from_json__default_constructible.output):

Ned Flanders (60) lives in 744 Evergreen Terrace

这里的要点:person 结构体定义在 namespace ns 中,from_json 也必须定义在同一命名空间。调用链为 j.get<ns::person>()get_impl(priority_tag<0>)person 默认可构造且 has_from_json 为真)→ adl_serializer<ns::person>::from_json(j, ret)::nlohmann::from_json(j, ret)。最后一个表达式中出现了 ns::person&,ADL 会同时搜索 namespace ns,于是用户定义的 ns::from_json 被优先匹配。

实战示例二:非默认可构造类型(特化 adl_serializer)

当类型不可默认构造时,无法使用“先构造、再填充”的 (1) 重载。官方示例 from_json__non_default_constructible.cpp 演示了另一条路径——直接特化 adl_serializer,提供返回 Tfrom_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_serializer<ns::person>
{
    static ns::person from_json(const json& j)
    {
        return {j.at("name"), j.at("address"), j.at("age")};
    }

    // Here's 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.get<ns::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_tag<0>)is_default_constructible 为假被 SFINAE 剔除;adl_serializer<ns::person> 特化提供的 T from_json(const json&) 使 has_non_default_from_json 成立,get_impl(priority_tag<1>) 被选中,执行 return JSONSerializer<ValueType>::from_json(*this);(见 json.hpp)。这也解释了 adl_serializer 第二个 from_json 重载中 identity_tag<TargetType> 的作用:库通过探测 from_json(j, identity_tag<TargetType>{}) 是否可调用,判断目标类型是否具备返回值风格的 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_json<nlohmann::ordered_map>),其 JSONSerializer 模板参数同样默认为 adl_serializer,用法完全一致。

to_json 一侧的库内支持函数集中在 to_json.hppfrom_json 一侧的内建重载(std::nullptr_tstd::optional、算术类型、字符串、容器等)集中在 from_json.hpp。用户自定义类型的自由函数与这些内建重载共同构成 ADL 的候选集:由于自由函数位于用户类型所在命名空间,对相应类型它总是优先于 nlohmann 命名空间内的同名重载被选中。

版本历史与延伸阅读

  • adl_serializer2.1.0 版本引入(与 from_jsonto_json 成员函数的版本标注一致)。
  • 相关测试用例集中在 unit-udt.cpp(用户自定义类型转换)与 unit-conversions.cpp 等文件中,可进一步验证 ADL 转换行为。
  • 成员函数完整文档:from_jsonto_json

小结adl_serializer 本身逻辑极简——两个 from_json 重载加一个 to_json,全部只是对 ::nlohmann::from_json/::nlohmann::to_json 的 SFINAE 转发;它真正的价值在于作为 basic_json 的默认 JSONSerializer,借助 ADL 把序列化职责下放给用户类型所在的命名空间。理解“默认可构造走输出参数重载、非默认可构造走返回值重载”这一选择规则,以及完全特化时必须补齐 to_json 的坑,即可覆盖绝大多数 UDT ↔ JSON 转换场景。

登录后查看全文
热门项目推荐
相关项目推荐