/c-plus-plus-serializer

A minimal C++11 header only serializer. Can serialize basic types, strings, containers, maps and custom classes. No suppprt yet for pointer types.

Primary LanguageC++GNU General Public License v2.0GPL-2.0

Simple C++ 11 header-only serializer

Have you ever wanted a really minimal header-only C++11 serializer? Of course you have! Having tried some other implementations I found them too hard to maintain and grok when they inevitably failed to compile under the latest g++ version. Hopefully this one is simple enough to avoid such issues. All you need to do is add

#include "c_plus_plus_serializer.h"

in your project for any files that need to do serialization.

The data is saved in raw binary format, hence this is only loadable on the same architecture that it was saved in.

Credits

This work was based on some of the ideas in this link, specifically, those by Samuel Powell.

Tested types

  • POD types (char, wchar_t, int, float, double etc...)
  • std::array (multidimensional works too)
  • std::list
  • std::map
  • std::deque
  • std::multiset
  • std::pair
  • std::set
  • std::string, std::wstring
  • std::unordered_map
  • std::vector
  • custom class
  • nested types e.g. std::map< std::string, std::list>

POD serialization

#include "c_plus_plus_serializer.h"

    static void serialize (std::ofstream out)
    {
        char           a = 42;
        unsigned short b = 65535;
        int            c = 123456;
        float          d = std::numeric_limits<float>::max();
        double         e = std::numeric_limits<double>::max();
        std::string    f("hello");
        wchar_t        g = L'💩';
        std::wstring   h(L"wide string 💩");

        out << bits(a) << bits(b) << bits(c) << bits(d);
        out << bits(e) << bits(f) << bits(g) << bits(h);
    }

    static void deserialize (std::ifstream in)
    {
        char           a;
        unsigned short b;
        int            c;
        float          d;
        double         e;
        std::string    f;
        wchar_t        g;
        std::wstring   h;

        in >> bits(a) >> bits(b) >> bits(c) >> bits(d);
        in >> bits(e) >> bits(f) >> bits(g) >> bits(h);
    }

Container serialization

#include "c_plus_plus_serializer.h"

    static void serialize (std::ofstream out)
    {
        std::initializer_list< std::string > d1 = {"vec-elem1", "vec-elem2"};
        std::vector< std::string > a(d1);

        std::initializer_list< std::string > d2 = {"list-elem1", "list-elem2"};
        std::list< std::string > b(d2);

        std::array< std::string, 2> c = {"arr-elem1", "arr-elem2"};

        //
        // 2d array
        //
        std::array< std::array<char, 2>, 3> d;
        d[0][0] = '0'; d[0][1] = '1';
        d[1][0] = '2'; d[1][1] = '3';
        d[2][0] = '4'; d[2][1] = '5';

        //
        // 3d array
        //
        std::array< std::array< std::array<char, 2>, 3>, 4> ddd;
        ddd[0][0][0] = 'a'; ddd[0][0][1] = 'b';
        ddd[0][1][0] = 'c'; ddd[0][1][1] = 'd';
        ddd[0][2][0] = 'e'; ddd[0][2][1] = 'f';
        ddd[1][0][0] = 'g'; ddd[1][0][1] = 'h';
        ddd[1][1][0] = 'i'; ddd[1][1][1] = 'j';
        ddd[1][2][0] = 'k'; ddd[1][2][1] = 'l';
        ddd[2][0][0] = 'm'; ddd[2][0][1] = 'n';
        ddd[2][1][0] = 'o'; ddd[2][1][1] = 'p';
        ddd[2][2][0] = 'q'; ddd[2][2][1] = 'r';
        ddd[3][0][0] = 's'; ddd[3][0][1] = 't';
        ddd[3][1][0] = 'u'; ddd[3][1][1] = 'v';
        ddd[3][2][0] = 'w'; ddd[3][2][1] = 'x';

        out << bits(a) << bits(b) << bits(c) << bits(dd) << bits(ddd);
    }

    static void deserialize (std::ifstream in)
    {
        std::string f;
        std::vector< std::string > a;
        std::list< std::string > b;
        std::array< std::string, 2> c;
        std::array< std::array<char, 2>, 3> d;
        std::array< std::array< std::array<char, 2>, 3>, 4> ddd;

        in >> bits(a) >> bits(b) >> bits(c) >> bits(dd) >> bits(ddd);
    }

Map serialization

    static void serialize (std::ofstream out)
    {
        std::map< std::string, std::string > m;
        m.insert(std::make_pair(std::string("key1"), std::string("value1")));
        m.insert(std::make_pair(std::string("key2"), std::string("value2")));
        m.insert(std::make_pair(std::string("key3"), std::string("value3")));
        m.insert(std::make_pair(std::string("key4"), std::string("value4")));
        out << bits(m);
    }

    static void deserialize (std::ifstream in)
    {
        std::map< std::string, std::string > m;
        in >> bits(m);
    }

Unordered map serialization

    static void serialize (std::ofstream out)
    {
        std::unordered_map< std::string, std::string > m;
        m.insert(std::make_pair(std::string("key1"), std::string("value1")));
        m.insert(std::make_pair(std::string("key2"), std::string("value2")));
        m.insert(std::make_pair(std::string("key3"), std::string("value3")));
        m.insert(std::make_pair(std::string("key4"), std::string("value4")));
        out << bits(m);
    }

    static void deserialize (std::ifstream in)
    {
        std::unordered_map< std::string, std::string > m;
        in >> bits(m);
    }

std::map< std::string, std::list > example

    static void serialize (std::ofstream out)
    {
        std::map< std::string, std::list< std::string > > m;

        std::initializer_list< std::string > L1 = {"list-elem1", "list-elem2"};
        std::list< std::string > l1(L1);
        std::initializer_list< std::string > L2 = {"list-elem3", "list-elem4"};
        std::list< std::string > l2(L2);

        m.insert(std::make_pair(std::string("key1"), l1));
        m.insert(std::make_pair(std::string("key2"), l2));

        out << bits(m);
    }

    static void deserialize (std::ifstream in)
    {
        std::map< std::string, std::list< std::string > > m;

        in >> bits(m);
    }

Set serialization

    static void serialize (std::ofstream out)
    {
        std::set< std::string > m;
        m.insert(std::string("key1"));
        m.insert(std::string("key2"));
        m.insert(std::string("key3"));
        m.insert(std::string("key4"));
        out << bits(m);
    }

    static void deserialize (std::ifstream in)
    {
        std::set< std::string > m;
        in >> bits(m);
    }

Multiset serialization

    static void serialize (std::ofstream out)
    {
        std::multiset< std::string > m;
        m.insert(std::string("key1"));
        m.insert(std::string("key1"));
        m.insert(std::string("key1"));
        m.insert(std::string("key1"));
        m.insert(std::string("key2"));
        m.insert(std::string("key3"));
        m.insert(std::string("key4"));
        out << bits(m);
    }

    static void deserialize (std::ifstream in)
    {
        std::multiset< std::string > m;
        in >> bits(m);
    }

Deque serialization

    static void serialize (std::ofstream out)
    {
        std::deque< std::string > m;
        m.push_front(std::string("key1"));
        m.push_front(std::string("key2"));
        m.push_back(std::string("key3"));
        m.push_back(std::string("key4"));
        out << bits(m);
    }

    static void deserialize (std::ifstream in)
    {
        std::deque< std::string > m;
        in >> bits(m);
    }

User defined class serialization

    class Custom {
    public:
        int a;
        std::string b;
        std::vector< std::string > c;

        friend std::ostream& operator<<(std::ostream &out,
                                        Bits<class Custom & > my)
        {
            out << bits(my.t.a) << bits(my.t.b) << bits(my.t.c);
            return (out);
        }

        friend std::istream& operator>>(std::istream &in,
                                        Bits<class Custom &> my)
        {
            in >> bits(my.t.a) >> bits(my.t.b) >> bits(my.t.c);
            return (in);
        }
    };

Serializing a custom template class

    template<class T > class MyPoint
    {
    public:
        T x {};
        T y {};

        MyPoint (void) : x(0), y(0) {};

        MyPoint (T x, T y) : x(x), y(y) { }

        friend std::ostream& operator<<(std::ostream &out,
                                        Bits<const MyPoint & > const my)
        {
            out << bits(my.t.x) << bits(my.t.y);
            return (out);
        }

        friend std::istream& operator>>(std::istream &in, Bits<MyPoint &> my)
        {
            in >> bits(my.t.x) >> bits(my.t.y);
            return (in);
        }

        friend std::ostream& operator << (std::ostream &out, const MyPoint &my)
        {
            out << "(" << my.x << ", " << my.y << ")";
            return (out);
        }
    };

    typedef MyPoint<int > IntPoint;
    typedef MyPoint<float > FloatPoint;
    typedef MyPoint<double > DoublePoint;

    static void serialize (std::ofstream out)
    {
        out << bits(IntPoint(1, 2));
        out << bits(FloatPoint(1.1, 2.2));
        out << bits(DoublePoint(3.3, 4.4));
    }

    static void deserialize (std::ifstream in)
    {
        IntPoint a;
        FloatPoint b;
        DoublePoint c;

        in >> bits(a);
        in >> bits(b);
        in >> bits(c);
    }

User defined class serialization (more complex one, a map of classes)

    class Custom {
    public:
        int a;
        std::string b;
        std::vector< std::string > c;

        friend std::ostream& operator<<(std::ostream &out,
                                        Bits<class Custom & > my)
        {
            out << bits(my.t.a) << bits(my.t.b) << bits(my.t.c);
            return (out);
        }

        friend std::istream& operator>>(std::istream &in,
                                        Bits<class Custom &> my)
        {
            in >> bits(my.t.a) >> bits(my.t.b) >> bits(my.t.c);
            return (in);
        }

        friend std::ostream& operator<<(std::ostream &out,
                                        class Custom &my)
        {
            out << "a:" << my.a << " b:" << my.b;

            out << " c:[" << my.c.size() << " elems]:";
            for (auto v : my.c) {
                out << v << " ";
            }
            out << std::endl;

            return (out);
        }
    };

    static void serialize (...)
    {
        std::map< std::string, class Custom > m;

        auto c1 = Custom();
        c1.a = 1;
        c1.b = "hello";
        std::initializer_list< std::string > L1 = {"vec-elem1", "vec-elem2"};
        std::vector< std::string > l1(L1);
        c1.c = l1;

        auto c2 = Custom();
        c2.a = 2;
        c2.b = "there";
        std::initializer_list< std::string > L2 = {"vec-elem3", "vec-elem4"};
        std::vector< std::string > l2(L2);
        c2.c = l2;

        m.insert(std::make_pair(std::string("key1"), c1));
        m.insert(std::make_pair(std::string("key2"), c2));

        out << bits(m);
    }

    static void deserialize (...)
    {
        std::cout << "read from " << filename << std::endl;
        std::ifstream in(filename);

        std::map< std::string, class Custom > m;

        in >> bits(m);
        std::cout << std::endl;

        std::cout << "m = " << m.size() << " list-elems { " << std::endl;
        for (auto i : m) {
            std::cout << "    [" << i.first << "] = " << i.second;
        }
        std::cout << "}" << std::endl;
    }

Bitfield serialization (C and C++ style)

    class BitsetClass {
    public:
        std::bitset<1> a;
        std::bitset<2> b;
        std::bitset<3> c;

        unsigned int d:1; // need c++20 for default initializers for bitfields
        unsigned int e:2;
        unsigned int f:3;
        BitsetClass(void) { d = 0; e = 0; f = 0; }

        friend std::ostream& operator<<(std::ostream &out,
                                        Bits<const class BitsetClass & > const my)
        {
            out << bits(my.t.a);
            out << bits(my.t.b);
            out << bits(my.t.c);

            std::bitset<6> s(my.t.d | my.t.e << 1 | my.t.f << 3);
            out << bits(s);

            return (out);
        }

        friend std::istream& operator>>(std::istream &in,
                                        Bits<class BitsetClass &> my)
        {
            std::bitset<1> a;
            in >> bits(a);
            my.t.a = a;

            in >> bits(my.t.b);
            in >> bits(my.t.c);
            std::bitset<6> s;
            in >> bits(s);

            unsigned long raw_bits = static_cast<unsigned long>(s.to_ulong());
            my.t.d = raw_bits & 0b000001;
            my.t.e = (raw_bits & 0b000110) >> 1;
            my.t.f = (raw_bits & 0b111000) >> 3;

            return (in);
        }
    };

Raw memory

#include "hexdump.h"

    auto elems = 128;

    static void serialize (std::ofstream out)
    {
        auto a = new char[elems];
        for (auto i = 0; i < elems; i++) {
            a[i] = i;
        }
        out << bits(a);
    }

    static void deserialize (std::ifstream in)
    {
        auto a = new char[elems];
        in >> bits(a);

        hexdump(a, elems);
    }

Output:

    128 bytes:
    0000 00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f |................|
    0010 10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f |................|
    0020 20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./|
    0030 30 31 32 33 34 35 36 37  38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?|
    0040 40 41 42 43 44 45 46 47  48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO|
    0050 50 51 52 53 54 55 56 57  58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\]^_|
    0060 60 61 62 63 64 65 66 67  68 69 6a 6b 6c 6d 6e 6f |`abcdefghijklmno|
    0070 70 71 72 73 74 75 76 77  78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.|

Compression

For compression, look at the following which uses quicklz

    zipped_container_example.cpp

Building

Do

    sh ./RUNME

Or, if that fails, manual build:

    c++ -std=c++11 -Werror -O2 -Wall -c -o .o/main.o main.cpp
    c++ .o/main.o -o c_plus_plus_serializer

To test:

    ./c_plus_plus_serializer

To debug, uncomment this line in Makefile.base, and then rerun RUNME:

#EXTRA_CFLAGS += -DDEBUG_C_PLUS_PLUS_SERIALIZER

To use 64-bit size_t when saving vectors, uncomment in Makefile.base and then rerun RUNME:

#EXTRA_CFLAGS += -DUSE_SIZE_T

Note, no sanity checking is performed on the data when it is read back in, so make sure your serializer and deserializers match perfectly.