跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 无反射 JSON 序列化:AIGCJson 的宏与模板魔法

AIGCJson 利用 C++ 预处理器宏将成员变量名转换为字符串,再通过变长参数模板在编译期展开参数包,配合 SFINAE 区分基础类型与自定义类型,实现了一行宏注册的 JSON 序列化/反序列化。文章剖析了字段名提取、递归参数绑定、类型路由分发,以及容器、嵌套、重命名和默认值等高级特性的实现原理。

SecGuard发布于 2026/6/3更新于 2026/8/1718 浏览

C++ 无反射 JSON 序列化:AIGCJson 的宏与模板魔法

C++ 缺少原生反射,要在运行时拿到成员变量的名称和类型几乎不可能,因此实现 JSON 序列化免不了大量样板代码。AIGCJson 的做法很取巧:靠预处理器宏和模板元编程,在编译期生成所有元数据和转换函数,让用户只写一行宏就能注册字段。

下面拆开看看它是怎么做到的。


怎么做到的?三步

AIGCJson 的套路可以总结成三步:

  1. 宏:利用 #__VA_ARGS__ 把成员变量列表原样变成字符串字面量,从而在运行时拿到字段名。
  2. 变长参数模板:把宏后面的参数包展开,在编译期逐一绑定到字段引用上。
  3. SFINAE:编译期判断类型,对基础类型、容器和自定义结构体走不同的处理分支。

AIGC_JSON_HELPER 宏

整个库的入口就是这个宏:

#define AIGC_JSON_HELPER(...) \
    std::map<std::string, std::string> __aigcDefaultValues; \
    bool AIGCJsonToObject(aigc::JsonHelperPrivate &handle, rapidjson::Value &jsonValue, std::vector<std::string>&names) { \
        std::vector<std::string> standardNames = handle.GetMembersNames(#__VA_ARGS__); \
        if (names.size() <= standardNames.size()) { \
            for (int i = names.size(); i < (int)standardNames.size(); i++) { \
                names.push_back(standardNames[i]); \
            } \
        } \
        return handle.SetMembers(names, 0, jsonValue, __aigcDefaultValues, __VA_ARGS__); \
    }

在类里写 AIGC_JSON_HELPER(name, age) 时,编译器会在该类中插入 AIGCJsonToObject 和 AIGCObjectToJson 两个成员函数,一个负责反序列化,一个负责序列化。

#__VA_ARGS__ 是预处理器字符串化操作符,它会把 name, age 直接变成字符串 "name, age",也就是说,字段名不是通过反射推断出来的,就是把源代码里的文本直接拿过去了。


字段名提取

拿到 "name, age" 这个字符串之后,JsonHelperPrivate::GetMembersNames 做的工作很简单:按逗号分割,去掉空格和引号,返回一个 vector<string> {"name", "age"},这就是 JSON 的 Key 了。

std::vector<std::string> GetMembersNames(const std::string membersStr) {
    std::vector<std::string> array = StringSplit(membersStr);
    StringTrim(array);
    return array;
}

这一步发生在运行期,没什么魔幻的。


变长参数模板递归:把名字和值绑到一起

有了 Key 字符串数组,还需要拿到对应的 Value,也就是成员变量的引用。AIGCJsonToObject 里调用 handle.SetMembers 时,传入了 __VA_ARGS__(即 name, age 这些变量)。这个函数使用变长参数模板,递归处理参数列表。

// 终止递归:处理最后一个参数
template<typename TYPE>
bool SetMembers(const std::vector<std::string>&names, int index, rapidjson::Value &jsonValue, ..., TYPE &arg) {
    const char* key = names[index].c_str();
    if (!jsonValue.HasMember(key)) {
        /* 处理默认值或返回 */
    }
    if (!JsonToObject(arg, jsonValue[key])) return false;
    return true;
}

// 递归展开函数
template<typename TYPE, typename... TYPES>
bool SetMembers(const std::vector<std::string>&names, int index, rapidjson::Value &jsonValue, ..., TYPE &arg, TYPES &...args) {
    if (!SetMembers(names, index, jsonValue, ..., arg)) return false;
    return SetMembers(++index, jsonValue, ..., args...);
}

假设注册了 name, age,调用流程是这样的:

  1. 进入展开函数,TYPE 是 string,arg 是 name。它调用单参数版本的 SetMembers,用 root["name"] 解析并赋值给 name。
  2. 然后递归调用自身,index 加 1,参数包剩下 age。
  3. 再次进入展开函数,TYPE 是 int,arg 是 age,同样调用单参数版本解析 root["age"]。
  4. 参数包为 0,递归结束。

就这样,运行期的 names 向量和编译期的参数包被按顺序一一对应起来了。这个递归展开是编译期完成的,所以没有运行时开销。


SFINAE 类型分发

SetMembers 内部解析值的时候,调用的是 JsonToObject(arg, jsonValue)。这个函数需要处理各种类型:int、string、vector、自定义结构体…… 库依赖 SFINAE 来判断类型并分发到不同实现。

首先用 HasConvertFunction 检查一个类型有没有 AIGCJsonToObject 成员函数:

template<typename T>
struct HasConvertFunction {
    template<typename TT>
    static char func(decltype(&TT::AIGCJsonToObject));
    template<typename TT>
    static int func(...);
    const static bool has = (sizeof(func<T>(nullptr)) == sizeof(char));
};

如果你的类用了 AIGC_JSON_HELPER 宏,它就包含 AIGCJsonToObject,has 就是 true。然后配合 enable_if 实现路由:

// 自定义类型
template<typename T, typename enable_if<HasConvertFunction<T>::has, int>::type = 0>
bool JsonToObject(T &obj, rapidjson::Value &jsonValue) {
    return obj.AIGCJsonToObject(*this, jsonValue, names);
}

// 基础类型(无 AIGCJsonToObject)
template<typename T, typename enable_if<!HasConvertFunction<T>::has, int>::type = 0>
bool JsonToObject(T &obj, rapidjson::Value &jsonValue) {
    if (std::is_enum<T>::value) { ... }
    return false; // 不支持的会报错
}

至于 int、string 这些基础类型,库里直接提供了全特化的重载:

bool JsonToObject(int& obj, rapidjson::Value &jsonValue);
bool JsonToObject(std::string &obj, rapidjson::Value &jsonValue);
// ...

容器与嵌套的自动处理

std::vector、std::map 之类的容器通过模板重载支持。以 vector 为例:

template<typename TYPE>
bool JsonToObject(std::vector<TYPE>&obj, rapidjson::Value &jsonValue) {
    if (!jsonValue.IsArray()) return false;
    auto array = jsonValue.GetArray();
    for (int i = 0; i < array.Size(); i++) {
        TYPE item;
        if (!JsonToObject(item, array[i])) return false;
        obj.push_back(item);
    }
    return true;
}

这里 TYPE 既可以是基础类型,也可以是自定义结构体。因为 JsonToObject(item, array[i]) 会再次进入 SFINAE 路由,碰上结构体就调用它的 AIGCJsonToObject。所以 vector<User> 也能正常工作。

嵌套结构就更自然了。解析 User.address 时:

  1. SetMembers 调用 JsonToObject(address, jsonValue["address"])。
  2. Address 类型有 AIGC_JSON_HELPER,SFINAE 把它送到自定义类型分支。
  3. 调用 address.AIGCJsonToObject(...),进入 Address 内部的整套逻辑。

整个过程一环套一环,不需要额外处理。


几个实用的高级特性

字段重命名:使用 AIGC_JSON_HELPER_RENAME 宏可以定义重命名函数,解析阶段的 JsonToObject 会优先检查是否存在这个函数,如果有就用它返回的 names 列表替换默认的字段名。AIGCJsonToObject 里还会做一次补齐:如果重命名列表长度不够,会用原名补足。

默认值:AIGC_JSON_HELPER_DEFAULT 宏生成了 AIGCDefaultValues,解析类似 "age=18" 的字符串并存入 __aigcDefaultValues 映射。解析 JSON 字段时,如果某个 Key 不存在,就到这个映射里找默认值,用 StringToObject 转换成对应类型赋给变量。

继承:AIGC_JSON_HELPER_BASE 宏生成的代码会调用 SetBase,这个函数只是简单转发调用基类的 JsonToObject,复用整套序列化逻辑。


这些机制凑在一起,让 AIGCJson 在不侵入类继承体系的情况下,把 RapidJSON 的 DOM 接口和 C++ 对象模型粘合得很干净。没有复杂的 DSL,没有代码生成步骤,只靠预处理和模板就做到了。理解了这套东西,再遇到类似需求时,自己动手扩展一下(比如加个新容器类型或换一种序列化格式)就心里有底了。

目录

  1. C++ 无反射 JSON 序列化:AIGCJson 的宏与模板魔法
  2. 怎么做到的?三步
  3. AIGCJSONHELPER 宏
  4. 字段名提取
  5. 变长参数模板递归:把名字和值绑到一起
  6. SFINAE 类型分发
  7. 容器与嵌套的自动处理
  8. 几个实用的高级特性
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Replay AI 翻唱工具教程:音轨分离与音色替换
  • PaddlePaddle 镜像运行 Stable Diffusion 的可行性分析
  • C++ List 容器实现原理与代码详解(下)
  • Qwen3.5 大模型单 GPU 高效部署与股票筛选应用
  • HarmonyOS 6.0+ 跨端智能写作助手开发:多设备接续与 AI 辅助
  • Pi0 机器人 VLA 大模型昇腾 A2 平台测评与部署指南
  • 算法实战:位运算求解两数之和、唯一数字及缺失数字
  • PPT 中嵌入 VR 全景图片与 Google 相机空间图片的操作方法
  • 基于 OpenClaw + 飞书实现 AI 新闻推送机器人
  • ClawX:基于 Electron 的可视化 AI 智能体工具
  • C++ 继承机制详解:访问控制、同名隐藏与派生类默认成员函数
  • 企业级工作流引擎低代码开发实战:RuoYi-Flowable-Plus 技术解析
  • 本地 LLM 模型与 Ollama、Python 集成实战
  • Python 列表内存存储本质:差异原因与优化建议
  • Java Swing 网格布局 GridLayout 基础用法
  • C++ 继承入门:从概念定义到默认成员函数详解
  • Stable Diffusion 的 3 个主流替代方案
  • Self-Instruct:基于 LLM 的指令数据集自动生成策略
  • Agent-Reach:零 API 成本实现 AI Agent 全网访问能力
  • DeepSeek-R1 大模型基于 MS-Swift 框架部署与微调实践

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online