Dabbling Seriously

Code, Coffee, and other Stuff.

Polymorphic JSON Serialization in C++

Introduction

This article documents an implementation of JSON which allows for the serialization
and deserialization of polymorphic C++ objects deriving from an interface type.
Previously I’ve written about how you can use a brief stub object to describe the
properties exposed from a serializable C++ object. In this article I’ll extend
the work that I’ve previously described, and this time describe how abstract base
classes can be serialized provided the types that derive from the base class are
pre-registered.

You can find the code used in this article here on github:
https://github.com/PhillipVoyle/json_h

Using the Code

The very first thing you will do is define the interface that will be your base
class. In this example I’m going to use a brief calculator example which has one
virtual method Execute, and then I’m going to derive three subclasses from that
abstract base class: An Add operation, Multiply, and then a terminal operation
Value, which returns a 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;
	   }
	};

Secondly you will want to expose the classes to JSON.h using a stub based on the JSON.h
preprocessor macros. As you can see, the interface descriptors are pretty much like the
class descriptors.

	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()

Finally, you can use the reader and writer mechanisms to load or store the
objects

	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;
	   }
	}

That was easy! For reference, here is the output

{"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

How it works

That’s all you need to know about how to serialize and deserialize polymorphic types
in C++ using JSON.h, but if you’re interested, I can show you some plumbing. First
I’ll show you the text of PointerTypeDescriptor.h, basically what this file does is
implements readers and writers for std::shared_ptr. This is not enough for serializing
polymorphic objects, for that we need interfaces too, which I’ll show you next

	#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

You will see a new generic in here called CreateAndRead, which takes a
ClassDescriptor, and which we will now provide an override for which
takes a new type InterfaceDescriptor. Below is the body of InterfaceDescriptor.h.
The file contains the descriptor type as mentioned, some new preprocessor macros,
which you will have seen used in the example above, and some methods for
reading and writing polymorphic types. Note the use of dynamic_cast. This is
an operation commonly used in higher level languages, but not so often in
C++ because of the overhead. Feel free to write to me and suggest another
mechanism – I only use this to determine the type of object I’m writing.
Also note the override of 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

Summing Up

Ok, so now you can use C++ to write JSON objects, this was fun, but maybe a little
wordy. This kind of thing still takes quite a bit of effort, but that’s going to
be the way it is for at least the next little while (a decade?) until the standards
committee agree on a reflection standard.

In my next blog I’m hoping to use some of this stuff to host a JSON web service in
a console app, using boost.asio. If you’re still interested, drop by.

Thanks for reading.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Information

This entry was posted on June 30, 2015 by in Uncategorized.
%d bloggers like this: