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

C++ 中的多态 JSON 序列化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (5投票s)

2015年6月30日

CPOL

3分钟阅读

viewsIcon

19828

downloadIcon

286

使用 JSON.h (版本 0.3,支持多态类型和 std::shared_ptr)

引言

本文档介绍了 JSON 的一种实现,该实现允许序列化和反序列化从接口类型派生的多态 C++ 对象。 以前我写过关于如何使用一个简短的存根对象来描述可序列化 C++ 对象公开的属性。 在本文中,我将扩展我之前描述的工作,这次描述如何序列化抽象基类,前提是预先注册了从基类派生的类型。 你可以在这里找到本文中使用的代码,网址是 github: https://github.com/PhillipVoyle/json_h

如果你有兴趣,也可以查看我之前的文章,在这里我的 文章页面:

或者在这里查看我的博客:https://dabblingseriously.wordpress.com/

Using the Code

你将要做的第一件事是定义将作为你的基类的接口。 在这个例子中,我将使用一个简短的计算器示例,它有一个虚拟方法 Execute,然后我将从那个抽象基类派生三个子类:一个 Add 操作,Multiply,然后是一个终端操作 Value,它返回一个值。

	#include "json/JSON.h"
	
	class IOperation
	{
	public:
	   virtual float Execute() = 0;
	};
	
	class Add : public IOperation
	{
	public:
	   std::shared_ptr<IOperation> operand1;
	   std::shared_ptr<IOperation> operand2;
	   Add()
	   {
	   }
	   Add(std::shared_ptr<IOperation> o1,
	      std::shared_ptr<IOperation> o2)
		  :operand1(o1), operand2(o2)
	   {
	   }
	   
	   float Execute()
	   {
	      return operand1->Execute() + operand2->Execute();
	   }
	};
	
	class Multiply : public IOperation
	{
	public:
	   std::shared_ptr<IOperation> operand1;
	   std::shared_ptr<IOperation> operand2;
	   Multiply()
	   {
	   }
	   Multiply(std::shared_ptr<IOperation> o1,
	      std::shared_ptr<IOperation> o2)
		  :operand1(o1), operand2(o2)
	   {
	   }
	   
	   float Execute()
	   {
	      return operand1->Execute() * operand2->Execute();
	   }
	};
	
	class Value: public IOperation
	{
	public:
	   float value;
	   
	   Value()
	   {
	      value = 0;
	   }
	   Value(float v): value(v)
	   {
	   }
	   
	   float Execute()
	   {
	      return value;
	   }
	};

其次,你将需要使用基于 JSON.h 预处理器宏的存根将类暴露给 JSON.h。 如你所见,接口描述符与类描述符非常相似。

	BEGIN_CLASS_DESCRIPTOR(Value)
	   CLASS_DESCRIPTOR_ENTRY(value)
	END_CLASS_DESCRIPTOR();
	
	BEGIN_CLASS_DESCRIPTOR(Add)
	   CLASS_DESCRIPTOR_ENTRY(operand1)
	   CLASS_DESCRIPTOR_ENTRY(operand2)
	END_CLASS_DESCRIPTOR()
	
	BEGIN_CLASS_DESCRIPTOR(Multiply)
	   CLASS_DESCRIPTOR_ENTRY(operand1)
	   CLASS_DESCRIPTOR_ENTRY(operand2)
	END_CLASS_DESCRIPTOR()
	
	BEGIN_INTERFACE_DESCRIPTOR(IOperation)
	   INTERFACE_DESCRIPTOR_ENTRY(Value)
	   INTERFACE_DESCRIPTOR_ENTRY(Add)
	   INTERFACE_DESCRIPTOR_ENTRY(Multiply)
	END_INTERFACE_DESCRIPTOR()

最后,你可以使用读取器和写入器机制来加载或存储对象

	void RWInterface()
	{
	   std::string sJSON = "{"
	      "\"type\":\"Add\","
	         "\"operand1\":{\"type\":\"Multiply\","
	            "\"operand1\":{\"type\":\"Value\",\"value\":10},"
	            "\"operand2\":{\"type\":\"Value\",\"value\":15}}"
	         ",\"operand2\":{\"type\":\"Value\",\"value\":26}}";
	
	   std::shared_ptr<IOperation> test;
	   FromJSON(test, sJSON);
	   
	   std::string sOut = ToJSON(test);
	   std::cout << sOut << std::endl;
	   
	   auto result =  test->Execute();
	   std::cout << "test->Execute() = " << result << std::endl;
	   if((sOut == sJSON) && (result == 176))
	   {
	      std::cout << "test pass" << std::endl;
	   }
	   else
	   {
	      std::cout << "test fail" << std::endl;
	   }
	}

真简单! 作为参考,这是输出

{"type":"Add","operand1":{"type":"Multiply","operand1":{"type":"Value","value":10},"operand2":{"type":"Value","value":15}},"operand2":{"type":"Value","value":26}}
test->Execute() = 176
test pass

工作原理

这就是你需要了解的关于如何使用 JSON.h 序列化和反序列化 C++ 中的多态类型的一切,但如果你有兴趣,我可以向你展示一些管道。 首先,我将向你展示 PointerTypeDescriptor.h 的文本,基本上,这个文件实现用于 std::shared_ptr 的读取器和写入器。 这对于序列化多态对象是不够的,为此,我们还需要接口,我将在接下来展示给你看

	#ifndef cppreflect_PointerTypeDescriptor_h
	#define cppreflect_PointerTypeDescriptor_h
	
	#include <memory>
	
	template<typename T, typename TReader>
	std::shared_ptr<T> CreateAndRead(
		const ClassDescriptor<T>& descriptor,
		TReader& reader)
	{
	   auto result = std::make_shared<T>();
	   ReadObject(reader, *result);
	   return result;
	}
	
	template<typename T>
	class ClassDescriptor;
	
	template<typename T>
	class PointerTypeDescriptor
	{
	};
	
	template<typename T>
	class ClassDescriptor<std::shared_ptr<T>>
	{
	public:
	   typedef PointerTypeDescriptor<std::shared_ptr<T>> descriptor_t;
	};
	
	template<typename TReader, typename T>
	void DispatchReadObject(
		const PointerTypeDescriptor<std::shared_ptr<T>> & descriptor,
		TReader &reader,
		std::shared_ptr<T>& t)
	{
	   typename ClassDescriptor<T>::descriptor_t desc;
	   t = CreateAndRead(desc, reader);
	}
	
	template<typename TWriter, typename T>
	void DispatchWriteObject(
		const PointerTypeDescriptor<std::shared_ptr<T>> & descriptor,
		TWriter &writer,
		const std::shared_ptr<T>& t)
	{
	   if(t == nullptr)
	   {
	      writer.WriteNull();
	   }
	   else
	   {
	      WriteObject(writer, *t.get());
	   }
	}
	
	#endif

你将在这里看到一个名为 CreateAndRead 的新泛型,它接受一个 ClassDescriptor,我们将为它提供一个重写,它接受一个新的类型 InterfaceDescriptor。 下面是 InterfaceDescriptor.h 的正文。 该文件包含前面提到的描述符类型,一些新的预处理器宏,你将在上面的示例中使用过,以及一些用于读取和写入多态类型的方法。 请注意 dynamic_cast 的使用。 这是一种在高层语言中常用的操作,但在 C++ 中并不常用,因为它有开销。 随时给我写信并提出另一种机制 - 我只用它来确定我正在编写的对象的类型。 还要注意 CreateAndRead 的重写。

	#ifndef cppreflect_InterfaceDescriptor_h
	#define cppreflect_InterfaceDescriptor_h
	
	template<typename T>
	class InterfaceDescriptor
	{
	};
	
	#define BEGIN_INTERFACE_DESCRIPTOR(X) \
	template<> class InterfaceDescriptor<X>; \
	template<> class ClassDescriptor<X> { public: typedef InterfaceDescriptor<X> descriptor_t;}; \
	template<> \
	class InterfaceDescriptor<X> { public: \
	   template<typename TCallback>\
	   void for_each_interface(TCallback callback) const\
	   {
	
	#define INTERFACE_DESCRIPTOR_ENTRY(X) callback(ClassDescriptor<X>::descriptor_t{});
	
	#define END_INTERFACE_DESCRIPTOR() \
	   } \
	};
	
	template<typename TInterface, typename TReader>
	class TryReadObjectFunctor
	{
	   std::shared_ptr<TInterface>& m_t;
	   TReader &m_reader;
	   std::string m_type;
	public:
	   TryReadObjectFunctor(std::shared_ptr<TInterface>& t, TReader &reader, const std::string& type):m_t(t), m_reader(reader), m_type(type)
	   {
	   }
	
	   template<typename TConcrete>
	   void operator()(ClassDescriptor<TConcrete> descriptor) const
	   {
	      if(m_type != descriptor.get_name())
	      {
	         return;
	      }
	      
	      auto t = std::make_shared<TConcrete>();
	      m_t = t;
	      
	      std::string sProperty;
	      while(!m_reader.IsEndObject())
	      {
	         m_reader.NextProperty(sProperty);
	         
	         ReadObjectFunctor<TReader, TConcrete> functor {m_reader, *t, sProperty};
	         descriptor.for_each_property(functor);
	         if(!functor.m_bFound)
	         {
	            throw std::runtime_error("could not find property");
	         }
	      }
	   }
	};
	
	template<typename TReader, typename T>
	std::shared_ptr<T> CreateAndRead(const InterfaceDescriptor<T> & descriptor, TReader &reader)
	{
	   std::shared_ptr<T> result;
	   reader.EnterObject();
	   if(!reader.IsEndObject())
	   {
	      std::string sProperty;
	      reader.FirstProperty(sProperty);
	      if(sProperty != "type")
	      {
	         throw std::runtime_error("expected type property");
	      }
	      std::string sType;
	      ReadObject(reader, sType);
	      
	      TryReadObjectFunctor<T, TReader> functor {result, reader, sType};
	      descriptor.for_each_interface(functor);
	   }
	   reader.LeaveObject();
	   
	   return result;
	}
	
	template<typename TInterface, typename TWriter>
	class TryWriteObjectFunctor
	{
	   const TInterface& m_t;
	   TWriter &m_writer;
	public:
	   TryWriteObjectFunctor(const TInterface& t, TWriter &writer):m_t(t), m_writer(writer)
	   {
	   }
	
	   template<typename TConcrete>
	   void operator()(ClassDescriptor<TConcrete> descriptor) const
	   {
	      try {
	         const TConcrete& concrete = dynamic_cast<const TConcrete&>(m_t); //todo: prefer faster mechanism?
	         
	         m_writer.BeginObject(descriptor.get_name());
	         m_writer.BeginProperty("type");
	         
	         WriteObjectFunctor<TWriter, TConcrete> functor(m_writer, concrete, false);
	         WriteObject(m_writer, std::string(descriptor.get_name())); //fixme: (allow const char*)
	         m_writer.EndProperty();
	         descriptor.for_each_property(functor);
	         m_writer.EndObject();
	      }
	      catch (std::bad_cast e)
	      {
	         //ignore it
	      }
	   }
	};
	
	template<typename TWriter, typename T>
	void DispatchWriteObject(const InterfaceDescriptor<T> & descriptor, TWriter &writer, const T& t)
	{
	   TryWriteObjectFunctor<T, TWriter> functor {t, writer};
	   descriptor.for_each_interface(functor);
	}
	
	#endif

总结

好的,现在你可以使用 C++ 编写 JSON 对象了,这很有趣,但可能有点冗长。 这种事情仍然需要相当大的努力,但这将是至少在下一个阶段(十年?)标准委员会就反射标准达成一致意见的方式。

在我的下一篇博客中,我希望使用其中的一些东西来使用 boost.asio 在控制台应用程序中托管 JSON Web 服务。 如果你仍然有兴趣,请来参观。

感谢阅读。 如果你喜欢这篇文章或我的代码,或者有任何其他评论,我很乐意收到你的来信,所以请给我留言,或者评论这篇文章。

这篇文章最初发布在这里:https://dabblingseriously.wordpress.com/2015/06/30/polymorphic-json-serialization-in-c/

© . All rights reserved.