Dabbling Seriously

Code, Coffee, and other Stuff.

Reading and Writing objects in C++ Part 3 – Parsing JSON

Quick Link: download json.h v0.1

Last week I posted an article exploring how you can format C++ objects to JSON using a hand coded reflection class. This week I’m going to go further, and present a parsing and formatting framework based on that work.

Many applications call for data to be stored or sent out of process. JSON has become one the more common serialisation standards because of it’s brevity and ease of implementation, however because C++ lacks a rich reflection mechanism, at least for the time being, often classes must be serialised by hand.

In the first part of this series I presented a basic reflection class and described how you can use it to implement a general serialisation procedure for objects with properties. You can find the first part of this series here:
Can you Serialize objects to and from C++?

In the second part of this series I elaborated on that class and described how you could use the class I originally presented to format C++ objects as JSON. You can read that article here:
Serializing Objects in C++ Part 2 – Writing JSON

In today’s post I’m going to go further and present a parsing and formatting framework based on that work.

To use the code you’ll have to download it and either tell your compiler to add the include folder to your include path, or just drop the code into your source directory.

Before I start I want to present a sample use of the code, and here it is

#include "json/JSON.h"

class AClass
{
public:
   int aValue;
   int anotherValue;
   std::string thirdValue;
};

class AClass2
{
public:
   std::string testing;
   std::string testing2;
   
   std::string moreTesting[3];
};

BEGIN_CLASS_DESCRIPTOR(AClass)
   CLASS_DESCRIPTOR_ENTRY(aValue)
   CLASS_DESCRIPTOR_ENTRY(anotherValue)
   CLASS_DESCRIPTOR_ENTRY(thirdValue)
END_CLASS_DESCRIPTOR();

BEGIN_CLASS_DESCRIPTOR(AClass2)
   CLASS_DESCRIPTOR_ENTRY(testing)
   CLASS_DESCRIPTOR_ENTRY(testing2)
   CLASS_DESCRIPTOR_ENTRY(moreTesting)
END_CLASS_DESCRIPTOR()

void ReadWriteJSONString()
{
   std::string sJSONParse =
      "{\"testing\":\"ABC\","
      "\"testing2\":\"DEF\","
      "\"moreTesting\":[\"\",\"xzxx\",\"\"]}";
      
   AClass2 result;
   FromJSON(result, sJSONParse);
   
   std::string sJSONWrite = ToJSON(result);
   std::cout << sJSONWrite << std::endl;
   
   if(sJSONParse == sJSONWrite)
   {
      std::cout << "test pass" << std::endl;
   }
   else
   {
      std::cout << "test fail";
   }
}

and here is the output. The test passes, so that’s nice.

{"testing":"ABC","testing2":"DEF","moreTesting":["","xzxx",""]}
test pass

If you’ve been following my blog, then some of this will look familiar. We have our two test classes AClass and AClass2, there are class descriptor stubs, and a method that will exercise the objects using FromJSON and ToJSON. What might look a little different, are the reflection stubs, that are now wrapped in preprocessor macros for brevity, and much of the plumbing is now hidden in JSON.h

Before I go on, I want to describe some new features.

  • Parsing, using ReadJSON(stream, object) and FromJSON(string, object)
  • Formatting using WriteJSON(stream, object) and string ToJSON(object)
  • std::map support providing the key type is std::string

If that’s all you need to know, go ahead and download the code.
json.h v0.1

Now let’s look at the plumbing. Here’s the class descriptor code and preprocessor macros for that class

//
//  ClassDescriptor.h
//  cppreflect
//
//  Created by Phillip Voyle on 21/06/15.
//  Copyright (c) 2015 PV. All rights reserved.
//

#ifndef cppreflect_ClassDescriptor_h
#define cppreflect_ClassDescriptor_h

template<typename T>
class PrimitiveTypeDescriptor
{
};

template<typename TClass>
class ClassDescriptor
{
public:
   typedef PrimitiveTypeDescriptor<TClass> descriptor_t;
};

#define BEGIN_CLASS_DESCRIPTOR(X) \
   template<> \
   class ClassDescriptor<X> \
   { \
   public: \
   typedef X class_t; \
      typedef ClassDescriptor<X> descriptor_t; \
      constexpr const char* get_name() const { return #X; } \
      template<typename TCallback> \
      void for_each_property(TCallback& callback) const {

#define CLASS_DESCRIPTOR_ENTRY(X) callback(#X, &class_t::X);

#define END_CLASS_DESCRIPTOR() }};

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

template <typename TWriter, typename TClass>
class WriteObjectFunctor
{
   TWriter& m_writer;
   const TClass& m_t;
   bool m_first;
public:
   WriteObjectFunctor(TWriter& writer, const TClass& t):m_writer(writer), m_t(t)
   {
      m_first = true;
   }
   
   template<typename TPropertyType>
   void operator()(const char* szProperty, TPropertyType TClass::*pPropertyOffset)
   {
      m_writer.BeginProperty(szProperty);
      WriteObject(m_writer, m_t.*pPropertyOffset);
      m_writer.EndProperty();
   }
};


template<typename TReader, typename T>
void DispatchReadObject(const PrimitiveTypeDescriptor<T>& descriptor, TReader &reader, T& t)
{
   reader.ReadValue(t);
}

template<typename TWriter, typename T>
void DispatchWriteObject(const ClassDescriptor<T>& descriptor, TWriter &writer, const T &t)
{
   WriteObjectFunctor<TWriter, T> functor(writer, t);
   writer.BeginObject(descriptor.get_name());
   descriptor.for_each_property(functor);
   writer.EndObject();
}

template <typename TReader, typename TClass>
class ReadObjectFunctor
{
   TReader& m_reader;
   TClass& m_t;
   std::string m_sProperty;
public:
   bool m_bFound;

   ReadObjectFunctor(TReader& reader, TClass& t, std::string sProperty):m_reader(reader), m_t(t), m_sProperty(sProperty)
   {
      m_bFound = false;
   }
   
   template<typename TPropertyType>
   void operator()(const char* szProperty, TPropertyType TClass::*pPropertyOffset)
   {
      if(m_sProperty == szProperty)
      {
         ReadJSON(m_reader, m_t.*pPropertyOffset);
         m_bFound = true;
      }
   }
};


template<typename TReader, typename T>
void DispatchReadObject(const ClassDescriptor<T>& descriptor, TReader &reader, T &t)
{
   reader.EnterObject();
   if(!reader.IsEndObject())
   {
      std::string sProperty;
      reader.FirstProperty(sProperty);
      for(;;)
      {
         ReadObjectFunctor<TReader, T> functor {reader, t, sProperty};
         descriptor.for_each_property(functor);
         if(!functor.m_bFound)
         {
            throw std::runtime_error("could not find property");
         }
         
         if(reader.IsEndObject())
         {
            break;
         }
         reader.NextProperty(sProperty);
      }
   }
   reader.LeaveObject();
}

template<typename TReader, typename T>
void ReadObject(TReader&reader, T& t)
{
   DispatchReadObject(GetTypeDescriptor(t), reader, t);
}

template<typename TWriter, typename T>
void DispatchWriteObject(const PrimitiveTypeDescriptor<T>& descriptor, TWriter &writer, const T& t)
{
   writer.WriteValue(t);
}

template<typename TWriter, typename T>
void WriteObject(TWriter& writer, const T& t)
{
   DispatchWriteObject(GetTypeDescriptor(t), writer, t);
}

#endif

You’ll see much of this code is similar to last week, with the exception of the reading and writing functions, which are now hidden in JSONLexer.h, JSONReader.h, and JSONWriter.h. These files deal specifically with the textual parts of the JSON format, whereas now ReadObject, WriteObject delegate to the different methods on the reader. Here’s JSONWriter.h so you can have a look. Again, not much different from last time. Although you’ll note that I’ve implemented the string escaping mechanism.

//
//  JSONWriter.h
//  cppreflect
//
//  Created by Phillip Voyle on 20/06/15.
//  Copyright (c) 2015 PV. All rights reserved.
//

#ifndef cppreflect_JSONWriter_h
#define cppreflect_JSONWriter_h

template<typename TStream>
class JSONWriter
{
  TStream& m_stream;
  bool m_first;
  
public:
  JSONWriter(TStream& stream):m_stream(stream)
  {
      m_first = false;
  }
  void BeginObject(const char* name)
  {
      m_stream << "{";
      m_first = true;
  }
  void EndObject()
  {
      m_stream << "}";
  }
  
  void BeginProperty(const char* name)
  {
      if(m_first)
      {
          m_first = false;
      }
      else
      {
          m_stream << ",";
      }
      
      m_stream << "\"" << name << "\":";
  }
  void EndProperty()
  {
  }
  
  void WriteValue(int value)
  {
      m_stream << value;
  }
  
  void WriteValue(float value)
  {
      m_stream << value;
  }
  
  void WriteValue(const std::string& str)
  {
      m_stream << "\"";
      for(char c :str)
      {
         switch(c)
         {
         case '\t':
            m_stream << "\\t";
            break;
         case '\f':
            m_stream << "\\f";
            break;
         case '\r':
            m_stream << "\\r";
            break;
         case '\n':
            m_stream << "\\n";
            break;
         case '\\':
            m_stream << "\\\\";
         case '"':
            m_stream << "\\\"";
            break;
         case '\b':
            m_stream << "\\b";
            break;
         default:
            m_stream << c;
            break;
         }
      }
      m_stream << "\"";
  }
  
  void WriteValue(bool b)
  {
      if(b)
      {
          m_stream << "true";
      }
      else
      {
          m_stream << "false";
      }
  }
  
  void BeginArray()
  {
      m_stream << "[";
      m_first = true;
  }
  
  void EndArray()
  {
      m_stream << "]";
  }
  
  void BeginItem()
  {
      if(m_first)
      {
          m_first = false;
      }
      else
      {
          m_stream << ",";
      }
  }
  
  void EndItem()
  {
  }
};

template<typename TStream>
JSONWriter<TStream> GetJSONWriter(TStream& stream)
{
  return JSONWriter<TStream>(stream);
}


#endif

I’ve also separated the Array and Vector code into their own header files ArrayTypeDescriptor.h and VectorTypeDescriptor.h, and there is now new code that implements the std::map logic – it now displays as a class. Here’s the code for that.

//
//  MapTypeDescriptor.h
//  cppreflect
//
//  Created by Phillip Voyle on 21/06/15.
//  Copyright (c) 2015 PV. All rights reserved.
//

#ifndef cppreflect_MapTypeDescriptor_h
#define cppreflect_MapTypeDescriptor_h

#include <map>

template<typename T>
class ClassDescriptor;

template<typename T>
class MapTypeDescriptor
{
};

template<typename T>
class ClassDescriptor<std::map<std::string, T>>
{
public:
   typedef MapTypeDescriptor<std::map<std::string, T>> descriptor_t;
};

template<typename TReader, typename T>
void DispatchReadObject(const MapTypeDescriptor<std::map<std::string, T>>& descriptor, TReader &reader, std::map<std::string, T>& t)
{
   reader.EnterObject();
   if(!reader.IsEndObject())
   {
      std::string sProperty;
      reader.FirstProperty(sProperty);
      for(;;)
      {
         ReadJSON(reader, t[sProperty]);
         if(reader.IsEndObject())
         {
            break;
         }
         reader.NextProperty(sProperty);
      }
   }
   reader.LeaveObject();
}


template<typename TWriter, typename T>
void DispatchWriteObject(const MapTypeDescriptor<std::map<std::string, T>>& descriptor, TWriter& writer, const std::map<std::string, T>& t)
{
   writer.BeginObject("map");
   for(auto it = t.begin(); it != t.end(); it++)
   {
      writer.BeginProperty(it->first.c_str());
      WriteJSON(writer, it->second);
      writer.EndProperty();
   }
   writer.EndObject();
}

#endif

And to use the code it’s not much different to the original use case. Just use ToJSON or FromJSON

   /* outputs {"10":"ABCDE","12":"ABCD"} */
   
   std::map test;
   test["12"] = "ABCD";
   test["10"] = "ABCDE";
   
   std::string mapString = ToJSON(test);
   std::cout << mapString << std::endl;

So where are we now? I’ve done most of the things that I wanted to do with this project. You can read and write from C++ objects, strings, maps, arrays and vectors, but there is still a couple of things left over. I still wanted to work on some code for polymorphic structures, so std::shared_ptr or T*, it doesn’t do that yet.

In my next blog I’m going to be adding polymorphism. Thanks for reading.

If you liked this blog, let me know! Leave a comment or email me here phillipvoyle@hotmail.com

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