C++ 对象序列化第 2 部分 - 编写 JSON





5.00/5 (1投票)
系列文章的第 2 部分。
引言
几天前,我写了一篇关于如何从 C++ 序列化对象的文章。将对象序列化为 C++ 和从 C++ 序列化(第 1 部分),从那时起,我做了一些更多的实验,现在有一些示例代码可以将 C++ 中的对象序列化为 JSON。 如果你还没有读过我的原文(上面的链接),你应该现在阅读它,这将帮助你入门。在上周的文章结尾,我有一个算法,该算法适用于任何具有 ClassDescriptor 特化形式的定义的反射对象的类。 本周,我想做的第一件事是重点介绍我对该类和模板策略所做的一些更改。
模板策略
首先是模板策略template<typename T>
class PrimitiveTypeDescriptor
{
};
template<typename T>
class ClassDescriptor
{
public:
typedef PrimitiveTypeDescriptor<T> descriptor_t;
};
正如你所看到的,现在有一个名为“descriptor_t”的类描述符的 typedef。 此类描述符描述了由描述符表示的类的类型。 这样做使我可以为原始类实现一种方法,而为不同类型的类实现另一种方法。
更新的类描述符
这是一些更新的示例类及其类描述符。
class AClass
{
public:
int aValue;
int anotherValue;
std::string thirdValue;
};
class AClass2
{
public:
std::string testing;
std::string testing2;
std::string moreTesting[3];
};
template<>
class ClassDescriptor<AClass>
{
public:
typedef ClassDescriptor<AClass> descriptor_t;
template<TCallback>
void for_each_property(TCallback& callback) const
{
callback("aValue", &AClass::aValue);
callback("anotherValue", &AClass::anotherValue);
callback("thirdValue", &AClass::thirdValue);
}
};
template<>
class ClassDescriptor<AClass2>
{
public:
typedef ClassDescriptor<AClass2> descriptor_t;
template<TCallback>
void for_each_property(TCallback& callback) const
{
callback("testing", &AClass2::testing);
callback("testing2", &AClass2::testing2);
callback("moreTesting", &AClass2::moreTesting);
}
};
好的,有什么变化? 首先,类描述符 typedef 指回类描述符本身,这意味着你只需要为每个要反射的类编写一次类描述符模板特化。 另一件事是 AClass2 类和描述符现在有一个名为 moreTesting 的数组属性。 这意味着我们现在还需要表示数组类。 以下是一些数组类型描述符
template<typename T>
class ArrayTypeDescriptor
{
};
template<typename T>
class ArrayTypeDescriptor<std::vector<T>>
{
public:
size_t get_size(std::vector<T>& vec) const
{
return vec.size();
}
};
template<typename T>
class ClassDescriptor<std::vector<T>>
{
public:
typedef ArrayTypeDescriptor<std::vector<T>> descriptor_t;
};
template<typename T, int N>
class ArrayTypeDescriptor<T[N]>
{
public:
size_t get_size(T(&t)[N]) const
{
return N;
}
};
template<typename T, int N>
class ClassDescriptor<T[N]>
{
public:
typedef ArrayTypeDescriptor<T[N]> descriptor_t;
};
好的,我在这里做了什么? 基本上,我表示了两种数组描述符,每种描述符都有一个 get_size 方法,你可以将其应用于所表示类型的实例。 还要注意,typedef 不是 ClassDescriptor typedef。 你稍后会明白我为什么这样做,但基本上是为了你可以为不同类别的对象提供不同的实现。 对象是类、原始类型还是数组。
序列化代码
最后,这是 JSON 序列化代码。 请原谅我:字符串没有转义,除了包含引号,这是另一个实验的工作,但我希望你可以看到在哪里插入该功能。
template<typename T>
typename ClassDescriptor::descriptor_t GetTypeDescriptor(const T& t)
{
return typename ClassDescriptor::descriptor_t {};
}
template<typename TStream, typename T>
void WriteJSON(TStream &stream, T& t);
template<typename TStream, typename TClass>
class WriteJSONFunctor
{
TStream& m_stream;
TClass& m_t;
bool m_first;
public:
WriteJSONFunctor(TStream& stream, TClass& t):m_stream(stream), m_t(t)
{
m_first = true;
}
template<typename TPropertyType>
void operator()(const char* szProperty, TPropertyType TClass::*pPropertyOffset)
{
if(m_first)
{
m_first = false;
}
else
{
m_stream << ",";
}
m_stream << "\"" << szProperty << "\": ";
WriteJSON(m_stream, m_t.*pPropertyOffset);
}
};
template<typename TStream, typename T>
void DispatchWriteJSON(const PrimitiveTypeDescriptor& descriptor,
TStream &stream, T& t)
{
stream << t;
}
template<typename TStream, typename T>
void DispatchWriteJSON(const PrimitiveTypeDescriptor& descriptor,
TStream &stream, std::string& t)
{
//todo: escape strings here
stream << "\"" << t << "\"";
}
template<typename TStream, typename T>
void DispatchWriteJSON(const ArrayTypeDescriptor& descriptor,
TStream &stream, T& t)
{
stream << "[";
auto size = descriptor.get_size(t);
for(int n = 0; n < size; n++)
{
if(n != 0)
{
stream << ",";
}
WriteJSON(stream, t[n]);
}
stream << "]";
}
template<typename TStream, typename T>
void DispatchWriteJSON(const ClassDescriptor& descriptor,
TStream &stream, T &t)
{
WriteJSONFunctor<TStream, T> functor(stream, t);
stream << "{";
descriptor.for_each_property(functor);
stream << "}";
}
template<typename TStream, typename T>
void WriteJSON(TStream &stream, T& t)
{
DispatchWriteJSON(GetTypeDescriptor(t), stream, t);
}
有四个 DispatchWriteJSON 重载:两个用于原始类型,包括一个用于字符串,一个用于其他所有类型,执行的方法由第一个参数中的元类决定,这意味着你不需要为你可以想到的每种类型编写一个函数,只要代码与这些函数之一相同即可:你可以看到字符串是一个例外,因此我们有一个用于字符串的特殊重载。 有一个函数用于编写集合 - 在这种情况下,集合将是一个数组,最后是一个用于编写类的函数,这与上一个博客中的函数没有太大区别,除了它的输出是 JSON 格式,运算符不是 const,并且只有写入(现在指定的)流,而不是读取。
客户端代码
我现在将把它放在一起,向你展示客户端代码和输出客户端代码
int main(int argc, const char * argv[])
{
AClass c1;
c1.aValue = 1;
c1.anotherValue = 2;
c1.thirdValue = "this is a test";
AClass2 c2;
c2.testing = "ABC";
c2.testing2 = "DEF";
c2.moreTesting[1] = "xzxx";
WriteJSON(std::cout, c1);
std::cout << std::endl;
WriteJSON(std::cout, c2);
std::cout << std::endl;
std::vector vec;
vec.push_back("1A");
vec.push_back("2B");
vec.push_back("3C");
WriteJSON(std::cout, vec);
std::cout << std::endl;
int c = 123;
WriteJSON(std::cout, c);
std::cout << std::endl;
return 0;
}
输出
{"aValue": 1,"anotherValue": 2,"thirdValue": "this is a test"} {"testing": "ABC","testing2": "DEF","moreTesting": ["","xzxx",""]} ["1A","2B","3C"] 123
摘要
那么我们在这里完成了什么?
很明显,现在给定一些元类,我们可以将 JSON 写入控制台。 我们唯一的内存分配用于存储的字符串和流中,我认为可以合理地实现整个过程而无需任何堆分配,这可能会使它非常快,除了我们正在使用 C++ 流之外,由于流类型是一个模板参数,我们可以替换它。
缺少什么?
显然,这只会将 C++ 类写入 JSON - 这可能对某些人有用,但我认为要真正有用,我们还需要从 JSON 读取。 JSON 格式仍然与对象树的迭代、访问紧密耦合,因此我想将它们分开。 这不支持多态类型,其中我们有一个指向基类型的指针或共享指针 - 这可能会使它更现实。 在我的下一篇博客中,我将开始解决这些问题