views:

184

answers:

4

I have a family of classes that contain only variables of the following types: std::string, int, double. I should be able to serialize/deserialize objects of these classes to/from C string (null terminated). I don't want to use some 3rdparty serializer and do not want to write full-featured serializer by myself. I will serialize/deserialize only once in my code.

So, how to write very very tiny and yet elegant serializer and do it pretty fast?

UPDATE

I've written and tested my own. Maybe it will be useful for someone. If you notice some bugs or have some suggestions how to make it better, let me know. Here it is:

  typedef std::ostringstream ostr;
  typedef std::istringstream istr;

  const char delimiter = '\n';
  const int doublePrecision = 15;

  void Save(ostr& os, int x) { os << x << delimiter; }

  void Save(ostr& os, double x)
  {
     os.precision(doublePrecision);
     os << x << delimiter;
  }

  void Save(ostr& os, const std::string& x) { os << x << delimiter; }

  void Load(istr& is, int& x)
  {
     is >> x;
     is.rdbuf()->sbumpc(); // get rid of delimiter
  }

  void Load(istr& is, double& x)
  {
     is >> x;
     is.rdbuf()->sbumpc(); // get rid of delimiter
  }

  void Load(istr& is, std::string& x) { getline(is, x, delimiter); }

Test:

     std::string a = "Test string 1 2 and 2.33";
     std::string b = "45";
     double c = 45.7;
     int d = 58;
     double e = 1.0/2048;

     std::ostringstream os;
     Save(os, a);
     Save(os, b);
     Save(os, c);
     Save(os, d);
     Save(os, e);
     std::string serialized = os.str();

     std::string aa;
     std::string bb;
     double cc = 0.0;
     int dd = 0;
     double ee = 0.0;

     std::istringstream is(serialized);
     Load(is, aa);
     Load(is, bb);
     Load(is, cc);
     Load(is, dd);
     Load(is, ee);

     ASSERT(a == aa);
     ASSERT(b == bb);
     ASSERT(c == cc);
     ASSERT(d == dd);
     ASSERT(e == ee);
+1  A: 

There are 2 ways of serializing string data to a stream, you either do it C-style and use a null to terminate it or (more portably and easier to read) first output a byte that says how long the string is then write the string.

Now if you want to differentiate a string from a non-string (number in this case), you can prepend every "packet" (item) with a byte code, say 0x00 for int, 0x01 for double, 0x02 for string, and branch a switch off depending on what the code is. This way you can even write the int/double as a byte, so you won't lose precision and you'll also end up with a smaller/easier to read file.

Blindy
I don't need to differentiate types. It is ok if I have to know that to read exactly. The question is how to serialize/deserialize string to/from stream. Thanks
bocco
That's the thing, the code itself is as easy as `os<<tobin(x.length())<<x`, where `tobin(int)` writes a number as binary. But once you reach this point you NEED to differentiate between strings (number + char array) and actual numbers. And you do that by splitting your stream into packets and each packet gets a "code" that identifies the packet type.
Blindy
Yes, I'm using '\n' for now, but I don't need to differentiate types.
bocco
A: 
void save(std::ostringstream& out, const std::string& x)
{
   out << x;
}


void read(std::istringstream& in, std::string& x)
{
   in.str(x);
}

from here.

luvieere
What if I serialize two string sequentially? How it will recognize that during deserialization?
bocco
You should separate them in some manner, maybe by adding endl (out << x << endl;) or another escaped character.
luvieere
in.str(x) doesn't read from stream. It resets the stream content to x.
bocco
A: 

I know you don't want to use a 3rdParty serializer, but if you would reconsider: use Boost.Serialization.

(even if this is not the answer for you, it might be for someone else stumbling on this question)

Very simple example

class some_data 
{
  public:
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & my_string;
       ar & my_double;
    }

  private:
    std::string my_string;
    double my_double;
};

and then to save:

my_data dataObject;

std::ofstream ofs("filename");
boost::archive::text_oarchive oa(ofs);
oa << dataObject;

or to load:

my_data dataObject;

std::ifstream ifs("filename");
boost::archive::text_iarchive ia(ifs);
ia >> dataObject;
Pieter
Actually I would rather recommend Google's Protocols Buffer. Better syntax and performances (compression rate / time).
Matthieu M.
Thanks for Google's Protocols Buffer. It is not for this task, but will be very very useful!
bocco
A: 

See the sun xdr format. It's binary and efficient.

I have a tiny custom class to do this, as an example:

    Marshall& Marshall::enc(const string& str) {
    size_t size = str.size();
    size_t pad = (4 - (size%4))%4;
    size_t size_on_buff = size + pad;
    space_for(sizeof(uint32_t) + size + pad);
    check_size_t_overflow(size);
    enc(static_cast<uint32_t>(size));
    // xdr mandates padding
    //space_for(size_on_buff);
    memcpy(&(*buff)[pos],str.data(), size);
    memset(&(*buff)[pos+size],0,pad);
    pos+=size_on_buff;
    return *this;
}

Marshall& Marshall::dec(string& str) {
    str.clear();
    size_t size;
    dec(size);
    size_t pad = (4 - (size%4))%4;
    size_t size_on_buff = size + pad;
    ck_space_avl(size + pad);
    //str.resize(size);
    str.assign((char*)&(*buff)[pos],size);
    pos+=size_on_buff;
    return *this;
}
piotr