65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2015年6月16日

CPOL

4分钟阅读

viewsIcon

17734

downloadIcon

271

系列文章的第 2 部分。

本文最初发布于此:https://dabblingseriously.wordpress.com/2015/06/16/serializing-objects-in-c-part-2-writing-json/

引言

几天前,我写了一篇关于如何从 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 格式仍然与对象树的迭代、访问紧密耦合,因此我想将它们分开。 这不支持多态类型,其中我们有一个指向基类型的指针或共享指针 - 这可能会使它更现实。 在我的下一篇博客中,我将开始解决这些问题

© . All rights reserved.