0

I'm working on serialising std::map using sstream in C++. Serialising function:

template <class key, class value>
std::string serializeMap(const std::map<key, value>& v){
    std::stringstream ss;
    std::for_each(v.begin(), v.end(), [&ss](const std::pair<key, value>& s){
            ss.write ((char*)&s, sizeof(std::pair<key,value>));
        });
    return ss.str();
}

And de-serialize using this:

template <class key, class value>
void deSerializeMap(const std::string& s, std::map<key, value>& v){

    std::stringstream ss1;

    ss1<<s;

    int pos = 0;
    printf("\nlen %d\n", s.size());
    while(ss1){
        char* ar = new char[sizeof(std::pair<key, value>)];
        ss1.read(ar, sizeof(std::pair<key, value>));
        v.insert(*(reinterpret_cast<std::pair<key, value>*>(ar)));
        pos+=sizeof(std::pair<key, value>);
        ss1.seekg(pos);
        delete[] ar;
    }

}

This works as expected using std::string. I had to use a C function that takes const char* as an argument, I tried using c_str() and adding a NULL character to the char array, but strlen gives zero. Is there something I'm missing?

Jishnu U Nair
  • 512
  • 5
  • 12
  • 29
  • 1
    try to write/read the key and value separately. As @user9169574 you cannot get the size of the pair with std::string with sizeof – Mihayl Jan 03 '18 at 20:21
  • I will try that. Thanks. – Jishnu U Nair Jan 03 '18 at 20:22
  • 3
    There's lots of issues here, but basically if you ever write serialization code for a generic data structure and you are not recursively calling serialize on the contained type, something is wrong. You're assuming that your keys and values are POD types; calling sizeof on a not-necessarily-pod types is code smell. Also, try not to use new and delete in such code; that is code smell too (and so is reinterpret_cast on a not-necessarily-pod type; lots of smells as you can see ;-) ). – Nir Friedman Jan 03 '18 at 20:42
  • You should also use the default overloads for types to make them serializable: `template std::ostream& operator<<(std::ostream& os, const std::map& map);` Implement the serialization function in terms of C++ overloads. Then, any map with types that support `operator<<` will also be supported. It will also work for any output stream, not just stringstreams. – Alex Huszagh Jan 04 '18 at 02:19

1 Answers1

1

As @Nir Friedman mentioned, if you're implementing a serializer and not calling it recursivley on your container's elements you are probably doing something wrong.

Making such recursive serialization means having POD types as the "atoms" (recursion ending condition) and the recursion step is to call the serializer on each member of a non-POD type.

A simple way to accomplish what you want is by having a function template like:

template <typename T>
uint64_t write(const T& t);

The write function usually returns an unsigned integer of how many bytes were serialized. This templated function should be specialized for all PODs and for any non POD type you wish to serialize. So in your case in addition to int, float, double, char, etc.. you need write(const std::map<K,T>& m) which goes over the map and for each key-value pair, calls write. Also usually for serialization, you write a header before the actual data, so that you could deserialize the data (e.g how would you know how many elements the map contained?).

Luckily (maybe) for you, the standard library has such a pattern (see "decorator" pattern) as mentioned by @ Alexander Huszagh, and that is the stream operator (operator<<) which can be called already on any POD type, so a (naive and) simple way to serialize a std::map would be:

template<typename K, typename V>
std::ostream& operator<<(std::ostream& os, const std::map<K,V>& m)
{
    os << "map" << '\n';             //write the type
    os << m.size() << '\n'; //write the size of the data
    for(auto&& kvp : m)
    {
        os << "pair" << '\n';
        os << kvp.first << '\n';  //Recursive call here...
        os << kvp.second << '\n'; //Recursive call here...
    }
    return os;
}

The above will be complicated to deserialize since the recursive calls might by just a number, char, or a string (with new lines) which will not have the "header" I added, but your code seems to assume it knows the Key and Value template arguments so it might be ok for you.

Anyway, you should not try to write a serializer yourself, it is hard work to make it correct and generic and also an utter waste of time since there are plenty publicly available on the web. Since you are intersted in serializing a map, I would suggest using some JSON library.

ZivS
  • 2,094
  • 2
  • 27
  • 48