参考资料:
参考资料:https://zhuanlan.zhihu.com/p/388454455
参考github地址:https://github.com/netcan/config-loader
背景
最近在做一个可配置系统的参数设置工具时,由于参数数量众多,包含多层树结构,并且参数之间还会有相互关系。
配置参数的读写,从技术选型上有ini,xml,json。由于ini原生不支持树结构,需要根据需要将父节点的option作为子节点的section,配置文件可读性变差。xml由于属性和元素分开的形式,不适合直接转为c++定义的树状结构。所有最终选择了json。
要想用c++实现一个通用的json文件序列化和反序列化工具,反射是绕不开的。c++语言原生不支持反射,因此要通过一些编程技巧实现。在查阅资料过程中发现了configloader,clone学习过程中发现该代码库目前仅实现了json文件的读取,写入还没有实现,因此,狗尾续貂,实现了json文件的写入,勉强是满足了笔者的项目需求。
原理
结构体定义
使用宏包装的结构体定义方法,包含了编译期字段数量计算,每个字段的名称和变量的封装,每个字段是否是最后一个字段判断(用来序列号json时最后一个字段不加“,”)。
#define DEFINE_SCHEMA(st, ...) \
struct st { \
template <typename, size_t> struct FIELD; \
static constexpr size_t _field_count_ = GET_ARG_COUNT(__VA_ARGS__); \
static constexpr decltype(#st) _schema_name_ = #st; \
EXPAND(PASTE(REPEAT_, GET_ARG_COUNT(__VA_ARGS__)) (FIELD_EACH, 0, __VA_ARGS__)) \
} \
每个字段
#define FIELD_EACH(i, arg) \
PAIR(arg); \
template <typename T> \
struct FIELD<T, i> { \
T& obj; \
FIELD(T& obj): obj(obj) {} \
auto value() -> decltype(auto) { \
return (obj.STRIP(arg)); \
} \
static constexpr const char* name() { \
return EXPAND(STRING(STRIP(arg))); \
} \
static constexpr const bool isLast() { \
return i==(_field_count_-1); \
} \
}; \
反序列化
反序列化时,使用jsoncpp解析json文件,然后采用递归的方式将json文件中的值填充到结构体中。
递归时,会根据当前节点的类型,选择对应的特化模板结构体中的反序列号函数。
序列化
序列化时,同样采用递归的方式。
//针对自定义结构体
static void dump(std::ostream& out, const T& obj, size_t depth = 0)
{
out << "{";
forEachField(obj, [&out, depth](auto&& fieldInfo) {
decltype(auto) fieldName = fieldInfo.name();
decltype(auto) value = fieldInfo.value();
out << "\n" << detail::indent(depth + 1) << "\"" << fieldName << "\"" <<":";
dump(out, value, depth + 1);
//如果是最后一个字段,则不加入","
if(!fieldInfo.isLast())
out << ",";
});
out << "\n" << detail::indent(depth) << "}";
}
//针对基本数据类型
static void dump(std::ostream& out, const T& obj, size_t = 0)
{
dump(out, obj);
}
//针对数字
static void dump(std::ostream& out, Number number)
{
out << number;
}
//针对字符串
static void dump(std::ostream& out, const std::string& str)
{
out << "\"" << str << "\"";
}
以上函数,被包含到一个模板结构体中。采用同一套模板,根据不同类型偏特,每种类型有各自的实现。
用法示例
结构体定义
DEFINE_SCHEMA(CorrectPara,
(int) bUseCorrect,
(int) eCorrectType,
(int) bAutoCorrect,
(int) nCorrectPeriod,
(int) nFilterRadius
);
DEFINE_SCHEMA(FilterSize,
(int) nW,
(int) nH,
(int) nShift
);
DEFINE_SCHEMA(FilterPara,
(int) bUseFilter,
(FilterSize) szFilterSize0,
(FilterSize) szFilterSize1
);
DEFINE_SCHEMA(ChannelPara,
(int) bChannelValid,
(FilterPara) filterPara
);
DEFINE_SCHEMA(Params,
(CorrectPara) CorrectPara,
(ChannelPara) ch0Para,
(ChannelPara) ch1Para
);
使用方法
Params paras;
auto ret = ReadJSONConfig(paras, "Params.json");
//当前暂不支持vector容器的序列化
WriteJsonConfig(paras, "ParamsEx.json");
仅两个接口,一个读取,一个写入。
读写的内容如下:
{
"CorrectPara":{
"bUseCorrect":0,
"eCorrectType":1,
"bAutoCorrect":2,
"nCorrectPeriod":100,
"nFilterRadius":3
},
"ch0Para":{
"bChannelValid":1,
"filterPara":{
"bUseFilter":1,
"szFilterSize0":{
"nW":128,
"nH":128,
"nShift":14
},
"szFilterSize1":{
"nW":4,
"nH":4,
"nShift":4
}
}
},
"ch1Para":{
"bChannelValid":1,
"filterPara":{
"bUseFilter":1,
"szFilterSize0":{
"nW":64,
"nH":64,
"nShift":12
},
"szFilterSize1":{
"nW":2,
"nH":32,
"nShift":6
}
}
}
}

本文介绍了如何使用C++实现Json配置数据的序列化和反序列化,背景是由于配置参数的复杂性选择了Json格式。通过静态反射技术,实现了结构体与Json之间的转换。文中提供了结构体定义、反序列化和序列化的原理,以及使用示例,包括读取和写入接口的说明。
6368

被折叠的 条评论
为什么被折叠?



