Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Carrying on from:

All the code is available from git hub: ThorsSerializer but only reviewing a small section here.

The idea is that using this library it should be easy to define how a class is serialized without writing any code. See example in README

#ifndef THORSANVIL_SERIALIZE_JSON_SERIALIZE_H
#define THORSANVIL_SERIALIZE_JSON_SERIALIZE_H

/* Content:
 *
 * Used to define how a class is imported/exported via the Json Serialized
 *
 * Conventions:
 *      T:      The type of the base class
 *      I:      The type of the member
 *      MP:     The type of the member pointer
 *
 *      S:      Source type (std::ostream output) (ThorsAnvil::Json::ScannerSax input)
 *
 * Every type that is serializeable has a class JsonSerializeTraits.
 * For basic types (int/float etc) no definition is required and the default template version works
 * For compound types you need to define the type SerializeInfo.
 *      SerializeInfo:           It is a mpl vector of types.
 *                              Each type (in the vector) defines how to serialize
 *                              a member of the compound type.
 *
 * Boilerplate  code to create the appropriate types for SerializeInfo.
 *      #define THORSANVIL_SERIALIZE_JsonAttribute(className, member)
 *
 * Example:
 *      class MyClass
 *      {
 *          // STUFF
 *
 *          // If any members are private and need to be serialized then
 *          // JsonSerializeTraits<MyClass> must be a friend of the class so it can generate the appropriate code
 *
 *          friend class JsonSerializeTraits<MyClass>;
 *      };
 *
 *      namespace ThorsAnvil { namespace Serialize { namespace Json {
 *      template<>
 *      class JsonSerializeTraits<MyClass>
 *      {
 *          static ThorsAnvil::Serialize::Json::JsonSerializeType const  type    = Map;
 *
 *          THORSANVIL_SERIALIZE_JsonAttribute(MyClass, member1);
 *          THORSANVIL_SERIALIZE_JsonAttribute(MyClass, member2);
 *          THORSANVIL_SERIALIZE_JsonAttribute(MyClass, member3);
 *          typedef boost::mps::vector<member1, member2, member3>   SerializeInfo;
 *      };
 *      }}}
 *
 * Now we can serialize/deserialize with:
 *      std::cout << jsonExport(myClassObj) << "\n";
 *      std::cin  >> jsonInport(myClassObj);
 *
 * Same for std::vector
 *      std::vector<MyClass>  vec;  // Will serialize any fundamental type or type with JsonSerializeTraits<> specialized for it
 *                                  // If JsonSerializeTraits<> is not specialized for a compound type you get a compile time
 *                                  // error
 *      std::cout << jsonExport(vec) << "\n";
 *      std::cin  >> jsonImport(vec);
 */
#include "json/ScannerSax.h"
#include "json/ParserShiftReduce.tab.hpp"
#include <boost/mpl/at.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/type_traits/is_fundamental.hpp>
#include <boost/typeof/typeof.hpp>
#include <iostream>


/*
 * Helper Macros:
 * 
 * These are macros that will build some boilerplate types needed by the serialization code.
 *
 * THORSANVIL_SERIALIZE_JsonAttribute:          This is the main macro used.
 *                                              It identifies a class member that will be serialized
 *
 * THORSANVIL_SERIALIZE_JsonAttribute_1:        Used internally (should probably not be used by others).
 * THORSANVIL_SERIALIZE_JsonAttributeAccess:    If you want to run some code to as part of the serialization processes
 *                                              this macro allows you to specify a type that will be used during serialization.
 *                                              Examples will be provided in the documentaion.
 *
 * THORSANVIL_SERIALIZE_JsonGenericMapAttributeAccess:  A generic accessor can be used to generate multiple items.
 *                                                      When de-serializing the Json can be applied to multiple elements.
 *                                                      Used manly for container classes like std::map
 *THORSANVIL_SERIALIZE_JsonGenericArrAttributeAccess:   A generic accessor used by for arrays rather than maps (std::vector)
 *                                                      But otherwise identical to THORSANVIL_SERIALIZE_JsonGenericMapAttributeAccess
 */
#define THORSANVIL_SERIALIZE_JsonAttribute(className, member)                                                                           \
    typedef BOOST_TYPEOF(((className*)01)->member)    JsonAttribute ## member ## Type;                                                  \
    THORSANVIL_SERIALIZE_JsonAttribute_1(className, member, JsonSerializeTraits<JsonAttribute ## member ## Type>)

#define THORSANVIL_SERIALIZE_JsonAttribute_1(className, member, SerTraits)                                                              \
    typedef BOOST_TYPEOF(&className::member)            JsonAttribute ## member ## TypePtr;                                             \
    typedef JsonSerialElementAccessor<className, JsonAttribute ## member ## TypePtr, SerTraits>  JsonAttribute ## member ## Accessor;   \
    struct member: JsonSerializeItem<className, JsonAttribute ## member ## Accessor, std::string>                                       \
    {                                                                                                                                   \
        member()                                                                                                                        \
            : JsonSerializeItem<className, JsonAttribute ## member ## Accessor, std::string>(#member, &className::member)               \
        {}                                                                                                                              \
    }
#define THORSANVIL_SERIALIZE_JsonAttributeAccess(className, member, accessor)                                                           \
    struct member: JsonSerializeItem<className, accessor, std::string>                                                                  \
    {                                                                                                                                   \
        member()                                                                                                                        \
            : JsonSerializeItem<className, accessor, std::string>(#member, accessor())                                                  \
        {}                                                                                                                              \
    }
#define THORSANVIL_SERIALIZE_JsonGenericMapAttributeAccess(className, accessor)                                                         \
    struct genericAccessor: JsonSerializeItem<className, accessor, std::string>                                                         \
    {                                                                                                                                   \
        genericAccessor()                                                                                                               \
            : JsonSerializeItem<className, accessor, int>(-1 , accessor())                                                              \
        {}                                                                                                                              \
    }



namespace ThorsAnvil
{
    namespace Serialize
    {
        /* External dependencies from the generic Serialization code */
        template<typename T, typename Parser>
        struct Importer;

        template<typename T, typename Printer>
        struct Exporter;

        namespace Json
        {

/* Three basic element types:   Invalid (this obejct is not a top level JSON object)
 *                              Map     A JSON object   { [<string> : <value> [, <string> : <value>]*]? }
 *                              Array   A JSON array    [ [<value> [, <value>]*]? ]
 */
enum JsonSerializeType {Invalid, Map, Array};

/*
 * All objects that want to be serialized by this code must define their own specialization of this class.
 * The default version will cause compilation errors. Which hopefully will bring the reader here.
 */
template<typename T>
struct JsonSerializeTraits
{
    static JsonSerializeType const  type    = Invalid;
    typedef T                     SerializeInfo;
};

// Forward declarations
template<typename T, typename A, typename RegisterKey>
struct JsonSerializeItem;

/* Class used by boost::mpl::for_each. Nothing special simple lamba's will replace them in the future */
/*
 *  T       The object Type
 *  S       The source type (parser or std::ostream)
 *
 * This is basically trying to templatize and remove the need for multiple action objects that
 * are called from mpl::for_each
 */
template<typename T, typename S>
struct MPLForEachActivateTrait;

template<typename T>
struct MPLForEachActivateTrait<T, std::ostream>
{
    typedef const T                 ObjectType;
    static int const                SerializeActionIndex    = 0;
};
template<typename T>
struct MPLForEachActivateTrait<T, ThorsAnvil::Json::ScannerSax>
{
    typedef T                       ObjectType;
    static int const                SerializeActionIndex    = 1;
};
template<typename T, typename S>
class MPLForEachActivateItem
{
    typedef          MPLForEachActivateTrait<T, S>  Traits;
    typedef typename Traits::ObjectType             ObjectType;

    S&              pump;
    ObjectType&     object;
    public:
        MPLForEachActivateItem(S& p, ObjectType& obj)
            : pump(p)
            , object(obj)
        {}
        // Depending on if the pump is a stream or a scanner
        // Calls      JsonSerialize::activate()
        // or         JsonDeSerialize::activate()
        template<typename SerializeItem>
        void operator()(SerializeItem const& item) const
        {
            typedef typename boost::mpl::at_c<typename SerializeItem::SerializeType, Traits::SerializeActionIndex>::type  SerializeAction;
            SerializeAction::activate(item, pump, object);
        }
};

/*
 * Objects of this type get stored in the
 *      JsonSerializeTraits::SerializeInfo
 *  This is what the user create with the macros above. The default A is JsonSerialElementAccessor
 *  But user can define their own action for complex objects This wrapper is merely a vehicle for
 *  calling the A methods in a controlled manner.
 *
 * Note: These classes are not designed to be used directly but via the macros:
 *          THORSANVIL_SERIALIZE_JsonAttribute
 *          THORSANVIL_SERIALIZE_JsonAttributeAccess
 *  See the notes by these macros for details
 */
template<typename T, typename A, typename RegisterKey, JsonSerializeType type = JsonSerializeTraits<T>::type>
struct JsonSerialize;


template<typename T, typename A, typename RegisterKey>
struct JsonSerialize<T, A, RegisterKey, Map>
{
    // Generic serialization of a JSON object
    static void activate(JsonSerializeItem<T, A, RegisterKey> const& item, std::ostream& stream, T const& src)
    {
        if (!item.first)
        {   stream << ',';
        }
        stream << '"' << item.memberName << '"' << ":";
        item.accessor.serialize(src, stream);
    }
};
template<typename C, typename A, typename RegisterKey>
struct JsonSerialize<C, A, RegisterKey, Array>
{
    // Generic serialization of a JSON array
    static void activate(JsonSerializeItem<C, A, RegisterKey> const& item, std::ostream& stream, C const& src)
    {
        if (!item.first)
        {   stream << ',';
        }
        item.accessor.serialize(src, stream);
    }
};

template<typename T, typename A,typename RegisterKey, JsonSerializeType type = JsonSerializeTraits<T>::type>
struct JsonDeSerialize;

template<typename T, typename A,typename RegisterKey>
struct JsonDeSerialize<T, A, RegisterKey, Map>
{
    static void activate(JsonSerializeItem<T, A, RegisterKey> const& item, ThorsAnvil::Json::ScannerSax& parser, T& dst)
    {
        std::auto_ptr<ThorsAnvil::Json::SaxAction>    action(item.accessor.action(dst));
        parser.registerAction(item.memberName, action);
    }
};
template<typename T, typename A>
struct JsonDeSerialize<T, A, int, Array>
{
    static void activate(JsonSerializeItem<T, A, int> const& item, ThorsAnvil::Json::ScannerSax& parser, T& dst)
    {
        std::auto_ptr<ThorsAnvil::Json::SaxAction>    action(item.accessor.action(dst));
        parser.registerActionOnAllArrItems(action);
    }
};

template<typename T, typename A>
struct JsonDeSerialize<T, A, std::string, Array>
{
    static void activate(JsonSerializeItem<T, A, std::string> const& item, ThorsAnvil::Json::ScannerSax& parser, T& dst)
    {
        std::auto_ptr<ThorsAnvil::Json::SaxAction>    action(item.accessor.action(dst));
        parser.registerActionNext(action);
    }
};

/*
 * A type holder object that picks up the correct versions of JsonSerialize and JsonDeSerialize
 * Used by MPLForEachActivateItem to get the correct type
 */
template<typename T, typename A, typename RegisterKey>
struct JsonSerializeItem
{
    RegisterKey memberName;
    A           accessor;
    bool        first;

    JsonSerializeItem(RegisterKey const& name, A const& ac): memberName(name), accessor(ac), first(false) {}
    JsonSerializeItem& makeFirst() {first = true;return *this;}

    typedef    JsonSerialize<T,A,RegisterKey>       Serialize;
    typedef    JsonDeSerialize<T,A,RegisterKey>     DeSerialize;

    typedef boost::mpl::vector<Serialize, DeSerialize>      SerializeType;
};
/*
 * Importing
 * ============
 *
 * The JsonImportAction defines a class that is registered with a Json SAX parser
 * so that we can register callback actions for particular attributes.
 *
 * For fundamental types json is read directly into the value.
 * For compound types when the attribute is reached additional callbacks are registered
 * for each of the compound members that needs to be de-serialized (this is done recursively)
 * So we can de-serialize arbitrary json structures.
 */
template<typename SerializeInfo, typename I, bool EnablePod = boost::is_fundamental<I>::value>
class JsonImportAction: public ThorsAnvil::Json::SaxAction
{
    I&              memberRef;
    public:
        JsonImportAction(I& mr)
            : memberRef(mr)
        {}

        virtual void doPreAction(ThorsAnvil::Json::ScannerSax&, ThorsAnvil::Json::Key const&){}
        virtual void doAction(ThorsAnvil::Json::ScannerSax&, ThorsAnvil::Json::Key const&, JsonValue const& value)
        {
            // Read fundamental type directly into the member
            memberRef   = value.getValue<I>();
        }
};
template<typename SerializeInfo, typename I>
class JsonImportAction<SerializeInfo, I, false>: public ThorsAnvil::Json::SaxAction
{
    I&              memberRef;
    public:
        JsonImportAction(I& mr)
            : memberRef(mr)
        {}

        virtual void doAction(ThorsAnvil::Json::ScannerSax&, ThorsAnvil::Json::Key const&, JsonValue const&){}
        virtual void doPreAction(ThorsAnvil::Json::ScannerSax& parser, ThorsAnvil::Json::Key const&)
        {
            // Compound type. Register callback for each member.
            //                This is done when the attribute is reached in json not before
            boost::mpl::for_each<SerializeInfo>(MPLForEachActivateItem<I, ThorsAnvil::Json::ScannerSax>(parser, memberRef));
        }
};
/*
 * Need a function template to create the correct JsonImportAction()
 */
template<typename SerializeInfo, typename T, typename I>
ThorsAnvil::Json::SaxAction* new_JsonImportAction(T& dst, I T::* memberPtr)
{
    return new JsonImportAction<SerializeInfo, I>(dst.*memberPtr);
}


/* Default Serialization Traits:
 * Used by all types without their own specific serialization traits.
 */
template<JsonSerializeType>
struct JsonSerializeBrace
{
    static char braces[];
};

/*
 * The MemberScanner is used to register callbacks that will read sub-members from the json object
 */
template<typename T, typename MemberToSerialize = typename JsonSerializeTraits<T>::SerializeInfo>
struct MemberScanner
{
    void operator()(ThorsAnvil::Json::ScannerSax& scanner, T& destination)
    {
        boost::mpl::for_each<typename JsonSerializeTraits<T>::SerializeInfo>(MPLForEachActivateItem<T, ThorsAnvil::Json::ScannerSax>(scanner, destination));
    }
};
template<typename T>
struct MemberScanner<T, T>
{
    // A normal type with no SerializeInfo has no members thus no need to register callbacks.
    void operator()(ThorsAnvil::Json::ScannerSax& scanner, T& destination)
    {}
};
template<typename T>
struct MemberScanner<T, void>
{
    // A normal type with no SerializeInfo has no members thus no need to register callbacks.
    void operator()(ThorsAnvil::Json::ScannerSax& scanner, T& destination)
    {}
};

/*
 * The MemberPrinter prints each member of an object.
 */
template<typename T, typename MemberToSerialize = typename JsonSerializeTraits<T>::SerializeInfo>
struct MemberPrinter
{
    void operator()(std::ostream& stream, T const& source)
    {
        typedef typename boost::mpl::at<typename JsonSerializeTraits<T>::SerializeInfo, boost::integral_constant<int,0> >::type  FirstType;
        typedef typename boost::mpl::pop_front<typename JsonSerializeTraits<T>::SerializeInfo>::type                             AllButFirstType;

        MPLForEachActivateItem<T, std::ostream>     itemPrinter(stream, source);


        // Print the first member (Call makeFirst() means no comma is printed)
        itemPrinter(FirstType().makeFirst());

        // For each of the other members use a loop a proceed each object with a comma
        boost::mpl::for_each<AllButFirstType>(itemPrinter);
    }
};
template<typename T>
struct MemberPrinter<T, T>
{
    // A normal object just prints itself.
    void operator()(std::ostream& stream, T const& source)
    {
        stream << source;
    }
};
template<typename T>
struct MemberPrinter<T, void>
{
    void operator()(std::ostream& stream, T const& source)
    {}
};


struct JsonSerializer
{
    template<typename T, JsonSerializeType base = JsonSerializeTraits<T>::type>
    struct Parser: ThorsAnvil::Json::ScannerSax
    {
        typedef boost::mpl::bool_<base != Invalid>      Parsable;
        Parser(T& dst)
            : destination(dst)
        {
            MemberScanner<T>    memberScanner;
            memberScanner(*this, destination);
        }

        void parse(std::istream& stream)
        {
            ScannerSax::parse<yy::ParserShiftReduce>(stream);
        }

        T&  destination;
    };

    template<typename T ,JsonSerializeType base = JsonSerializeTraits<T>::type>
    struct Printer
    {
        typedef boost::mpl::bool_<base != Invalid>      Printable;
        Printer(T const& src)
            : source(src)
        {}

        void print(std::ostream& stream)
        {
            stream << JsonSerializeBrace<base>::braces[0];

            MemberPrinter<T>    memberPrinter;
            memberPrinter(stream, source);

            stream << JsonSerializeBrace<base>::braces[1];
        }

        T const&    source;
    };

};

/*
 * Default accessors for fundamental types std::string
 * The serialize()  Recursively calls jsonInternalExport() on the member to serialize the member.
 *
 * The action()     Creates a JsonImportAction() that is registered with the SAX parser that just reads the
 *                  Item directly into the object. If the object is a compound type it uses the SerializeInfo
 *                  to register subsequent actions recursively so we always read directly into an object
 *                  not a copy.
 */
template<typename T, typename MP, typename SerTraits>
class JsonSerialElementAccessor
{
    MP          memberPtr;
    public:
    JsonSerialElementAccessor(MP mp): memberPtr(mp)    {}
    void serialize(T const& src, std::ostream& stream) const
    {
        stream << jsonInternalExport(src.*memberPtr);
    }
    std::auto_ptr<ThorsAnvil::Json::SaxAction>      action(T& dst) const
    {
        std::auto_ptr<ThorsAnvil::Json::SaxAction>  action(new_JsonImportAction<typename SerTraits::SerializeInfo>(dst, memberPtr));
        return action;
    }
};

/* Helper functions */
template<typename T>
Importer<T, typename JsonSerializer::template Parser<T> >   jsonInternalImport(T& object)
{
    return Importer<T, typename JsonSerializer::template Parser<T> >(object);
}

template<typename T>
Exporter<T, typename JsonSerializer::template Printer<T> >  jsonInternalExport(T const& object)
{
    return Exporter<T, typename JsonSerializer::template Printer<T> >(object);
}

        }
    }
}


#endif
share|improve this question
3  
I strongly doubt that Loki would name something after Thor. Unless it was to ingratiate himself for some nefarious scheme. –  Konrad Rudolph Apr 24 '12 at 12:41
3  
@KonradRudolph: Note: Its 'Thor's Anvil'. The item that Thor beats his frustration out on with his hammer(Mjölnir). Thus in my weird though patterns another pseudonym for Loki. (not the Loki was Thor's only frustration and Nemesis but he was a bit of pain). –  Loki Astari Apr 24 '12 at 15:55
2  
One level deeper. You win. –  Konrad Rudolph Apr 24 '12 at 16:44
    
@LokiAstari: If you've come up with anything for this during these past two years, feel free to provide a self-answer review. It's still eligible for answer badges and, of course, the bounty. –  Jamal May 10 at 20:11

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.