Dabbling Seriously

Code, Coffee, and other Stuff.

Serializing Objects in C++ Part 2 – Writing JSON

Serializing objects to and from C++ (Part 1)

A few days ago I wrote an article on how to serialize objects from C++. Since then I’ve been doing some more experiments and now have some sample code that serializes objects from C++ to JSON. If you haven’t read my original article (link above) you should read it now, and that will get you started.

At the end of last week’s post I had one algorithm that worked for any class that had a defined reflection object in the form of a ClassDescriptor specialization. This week the first thing I want to do is hi-light some changes that I’ve made to that class and the template policy.

First here’s the template policy:

template
class PrimitiveTypeDescriptor
{
};

template
class ClassDescriptor
{
public:
   typedef PrimitiveTypeDescriptor descriptor_t;
};

As you can see there is now a typedef for the class descriptor called ‘descriptor_t’. This class describes the type of class that is represented by the descriptor. Doing this allows me to implement one method for classes that are primitive and one for classes of a different type. Here’s some updated sample classes and their class descriptors.

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
{
public:
   typedef ClassDescriptor descriptor_t;
   
   template
   void for_each_property(TCallback& callback) const
   {
      callback("aValue", &AClass::aValue);
      callback("anotherValue", &AClass::anotherValue);
      callback("thirdValue", &AClass::thirdValue);
   }
};

template
class ClassDescriptor
{
public:
   typedef ClassDescriptor descriptor_t;
   
   template
   void for_each_property(TCallback& callback) const
   {
      callback("testing", &AClass2::testing);
      callback("testing2", &AClass2::testing2);
      callback("moreTesting", &AClass2::moreTesting);
   }
};

Ok, so what’s changed? First of all, the class descriptor typedef refers back to the class descriptor itself, which means that you only have to write the class descriptor template specialization once for each class you want to reflect. The other thing is that the AClass2 class and descriptor now has an array property called moreTesting. This means that we now also need to represent classes that are arrays. Here are some array type descriptors:

template
class ArrayTypeDescriptor
{
};

template
class ArrayTypeDescriptor<std::vector>
{
public:
   size_t get_size(std::vector& vec) const
   {
      return vec.size();
   }
};

template
class ClassDescriptor<std::vector>
{
public:
   typedef ArrayTypeDescriptor<std::vector> descriptor_t;
};

template
class ArrayTypeDescriptor
{
public:
   size_t get_size(T(&t)[N]) const
   {
      return N;
   }
};

template
class ClassDescriptor
{
public:
   typedef ArrayTypeDescriptor descriptor_t;
};

Ok, so what have I done here? Basically I’ve represented two kinds of array descriptors, each with a get_size method that you can apply to an instance of the represented type. Also note that the typedef is not the ClassDescriptor typedef. You’ll see why I did this in a moment but basically it’s so that you can provide different implementations for different categories of objects. Is the object a class, a primitive, or an array.

So finally, here is the JSON serialization code. You’ll have to forgive me: the strings aren’t escaped except for including quotes, that’s a job for another experiment but I hope you can see where that functionality would be inserted.

template
typename ClassDescriptor::descriptor_t GetTypeDescriptor(const T& t)
{
   return typename ClassDescriptor::descriptor_t {};
}

template
void WriteJSON(TStream &stream, T& t);

template
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
   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
void DispatchWriteJSON(const PrimitiveTypeDescriptor& descriptor, TStream &stream, T& t)
{
   stream << t;
}

template
void DispatchWriteJSON(const PrimitiveTypeDescriptor& descriptor, TStream &stream, std::string& t)
{
   //todo: escape strings here
   stream << "\"" << t << "\"";
}

template
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
void DispatchWriteJSON(const ClassDescriptor& descriptor, TStream &stream, T &t)
{
   WriteJSONFunctor functor(stream, t);
   stream << "{";
   descriptor.for_each_property(functor);
   stream << "}";
}

template
void WriteJSON(TStream &stream, T& t)
{
   DispatchWriteJSON(GetTypeDescriptor(t), stream, t);
}

There’s four overloads of DispatchWriteJSON: Two for primitives, including one for strings, and one for everything else, the method executed is decided on the metaclass in the first parameter, which means you don’t have to write a function for every type you can think of as long as the code is going to be the same as one of these functions: you can see that the string is an exception and so we have a special overload for that. There’s one function for writing collections – in this case the collection will be an array, and finally there is the one function for writing the classes, which is not that different from the one in the last blog, except for that it’s output is in JSON format, the operator is not const, and there are only writes to the (now specified) stream, instead of reads.

I’m going to put this together now and show you the client code and an output

Client Code:

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

Output:

{"aValue": 1,"anotherValue": 2,"thirdValue": "this is a test"}
{"testing": "ABC","testing2": "DEF","moreTesting": ["","xzxx",""]}
["1A","2B","3C"]
123

So what have we accomplished here? It’s pretty clear that we can write JSON to the console now, given a few metaclasses. Our only memory allocation here is for the strings stored and in the stream, and I think it could be plausible that you could implement this whole thing without any heap allocations, which could make it really quick apart from the fact that we’re using C++ streams, which since the stream type is a template parameter, we could replace.

What’s Missing?
Obviously this only writes C++ classes to JSON – this might be useful to some people, but I think to be really useful we’d also want to read from JSON. The JSON format is still tightly coupled with the iteration, visitation of the object tree, so I’d like to separate that. This doesn’t support polymorphic types where we have a pointer or maybe a shared_ptr to a base type – that might make it more realistic. In my next blog I’ll start addressing these things

Advertisements

2 comments on “Serializing Objects in C++ Part 2 – Writing JSON

  1. Pingback: Can you Serialize objects to and from C++? | Dabbling Seriously

  2. Pingback: Serializing Objects in C++ Part 3 – Parsing JSON | Dabbling Seriously

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 16, 2015 by in Uncategorized and tagged , , .
%d bloggers like this: