跳到主要内容AIGCJson 库源码解析:宏与模板实现的 JSON 序列化 | 极客日志C++AI算法
AIGCJson 库源码解析:宏与模板实现的 JSON 序列化
C++ 原生不支持反射,AIGCJson 库通过预处理器宏和模板元编程在编译期生成序列化代码。核心利用 #__VA_ARGS__ 获取字段名,变长参数模板处理字段值,SFINAE 区分自定义类型与基础类型,支持容器嵌套及默认值配置。实现了无需修改继承关系的轻量级 JSON 序列化方案。
SecGuard2 浏览 AIGCJson 库源码解析:宏与模板实现的 JSON 序列化
引言
C++ 是一门静态类型语言,原生不支持反射(Reflection)。这意味着在运行时,程序无法像 Java 或 C# 那样直接获取类的成员变量名称、类型等信息。因此,实现 JSON 序列化通常比较麻烦,往往需要大量的样板代码。
AIGCJson 库通过一种巧妙的方式解决了这个问题:利用 C++ 预处理器宏 和 模板元编程,在编译期生成必要的元数据和转换代码,从而实现了'一行代码注册'的极简体验。
本文将深入源码,剖析 AIGCJson 是如何通过一行宏 AIGC_JSON_HELPER 实现自动化序列化的。
核心设计:宏与模板的共舞
AIGCJson 的核心逻辑可以概括为以下三步:
- 宏(Macro):利用
#__VA_ARGS__ 将成员变量列表转换为字符串,从而在运行时获取字段名称。
- 模板(Template):利用变长参数模板(Variadic Templates),将宏传递的参数包展开,从而在编译期获取字段值的引用。
- 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__
# 是预处理器的字符串化操作符。#__VA_ARGS__ 会将宏的变长参数原样转换为一个字符串字面量。
例如 AIGC_JSON_HELPER(name, age) 会被展开为:
handle.GetMembersNames("name, age");
这就是 AIGCJson 获取字段名称的秘诀。它没有使用真正的反射,而是直接拿到了你写的代码文本字符串。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
静态反射的模拟:字段名提取
有了 "name, age" 这样的字符串,JsonHelperPrivate::GetMembersNames 的工作就很简单了:
std::vector<std::string> GetMembersNames(const std::string membersStr) {
std::vector<std::string> array = StringSplit(membersStr);
StringTrim(array);
return array;
}
这个函数在运行时执行,解析出 {"name", "age"} 这样的字符串数组,作为 JSON 的 Key。
递归的艺术:变长参数模板
获取了字段名(Key),如何获取字段值(Value)并与 Key 对应起来呢?
AIGCJsonToObject 中调用了 handle.SetMembers,并将 __VA_ARGS__(即 name, age)作为参数传递进去。这里利用了 C++11 的变长参数模板。
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...);
}
递归过程详解
假设 AIGC_JSON_HELPER(name, age),调用 SetMembers(names, 0, root, ..., name, age):
- 进入递归展开函数:
TYPE 是 string,arg 是 name。
- 调用
SetMembers(..., 0, ..., name)(单参数版本):解析 root["name"] 并赋值给 name。
- 递归调用
SetMembers(..., 1, ..., age)。
- 再次进入递归展开函数(或者如果编译器优化,直接匹配到终止函数):
TYPE 是 int,arg 是 age。
- 调用
SetMembers(..., 1, ..., age)(单参数版本):解析 root["age"] 并赋值给 age。
- 没有更多参数,递归结束。
通过这种方式,AIGCJson 将字段名列表(运行时的 vector)和字段引用列表(编译期的参数包)一一对应了起来。
类型分发:SFINAE 的应用
在 SetMembers 内部,真正的脏活累活是由 JsonToObject(arg, jsonValue) 完成的。这个函数需要处理各种类型:int、vector、自定义结构体等。
AIGCJson 使用 SFINAE(Substitution Failure Is Not An Error)技术来区分自定义类型和基础类型。
检测是否为自定义类型
库中定义了一个检测器 HasConvertFunction:
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 函数,HasConvertFunction<T>::has 就会为 true。
路由分发
利用 enable_if,JsonHelperPrivate 实现了函数的'路由':
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);
}
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 等基础类型,AIGCJson 提供了大量的特化重载函数:
bool JsonToObject(int& obj, rapidjson::Value &jsonValue);
bool JsonToObject(std::string &obj, rapidjson::Value &jsonValue);
容器与嵌套结构的处理
容器支持
对于 std::vector、std::map 等容器,AIGCJson 提供了模板重载:
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 address 字段时:
SetMembers 调用 JsonToObject(address, jsonValue["address"])。
Address 类型有 AIGC_JSON_HELPER,触发 SFINAE 路由到自定义类型处理函数。
- 调用
address.AIGCJsonToObject(...)。
- 进入
Address 内部的解析逻辑。
高级特性原理
成员重命名
宏 AIGC_JSON_HELPER_RENAME 定义了 AIGCRenameMembers 函数:
#define AIGC_JSON_HELPER_RENAME(...) \
std::vector<std::string> AIGCRenameMembers(aigc::JsonHelperPrivate &handle) { \
return handle.GetMembersNames(#__VA_ARGS__); \
}
在解析时,JsonToObject 会先检查是否有重命名函数:
std::vector<std::string> names = LoadRenameArray(obj);
return obj.AIGCJsonToObject(*this, jsonValue, names);
如果有,names 列表会被替换为重命名后的列表,传递给 AIGCJsonToObject。注意 AIGCJsonToObject 内部有一个判断:
if (names.size() <= standardNames.size())
默认值
宏 AIGC_JSON_HELPER_DEFAULT 定义了 AIGCDefaultValues 函数,解析默认值字符串(如 "age=18")并存入 __aigcDefaultValues Map 中。
if (!jsonValue.HasMember(key)) {
std::string defaultV = FindStringFromMap(names[index], defaultValues);
if (!defaultV.empty())
StringToObject(arg, defaultV);
return true;
}
继承支持
AIGC_JSON_HELPER_BASE 宏实际上生成了调用基类转换函数的代码:
#define AIGC_JSON_HELPER_BASE(...) \
template<typename TYPE>
bool SetBase(rapidjson::Value &jsonValue, TYPE *arg) {
return JsonToObject(*arg, jsonValue);
}
这里 *arg 是切片后的基类对象引用,调用 JsonToObject 会触发 SFINAE 路由,最终调用基类的 AIGCJsonToObject。
总结
AIGCJson 的源码展示了 C++ 模板元编程的强大威力。它没有引入复杂的反射框架,仅仅利用编译器特性就实现了优雅的序列化方案。
- 非侵入性:不需要修改类继承关系,只需添加宏。
- 编译期计算:类型检查和函数分发大多在编译期完成。
- 无缝胶水:巧妙地将 RapidJSON 的 DOM API 与 C++ 对象模型粘合在一起。
通过理解这些原理,我们不仅能更好地使用这个库,也能在需要时对其进行扩展(例如支持新的容器类型或序列化格式)。