A simple way to achieve a semblance of versioning is to make sense of the members of objects you are serializing. If your code has an understanding of the various types of data to be serialized you can get some robustness without doing too much work.
Say we have a serialized object that looks like this:
ObjectType
{
m_name = "a string"
m_size = { 1.2, 2.1 }
m_someStruct = {
m_deeperInteger = 5
m_radians = 3.14
}
}
It should be easy to see that the type ObjectType
has data members called m_name
, m_size
and m_someStruct
. If you can loop over or enumerate data members during run-time (somehow) then when reading this file you can read in a member name and match it up to an actual member within your object instance.
During this lookup phase if you don't find a matching data member you can safely ignore this portion of the save file. For example say version 1.0 of SomeStruct
had a m_name
data member. Then you patch and this data member was removed entirely. When loading your save file you will come across m_name
and lookup a matching member and find no match. Your code can simply move on to the next member in the file without crashing. This lets you remove data members without any worries about breaking old save files.
Similarly if you add in a new type of data member and try to load from an old save file your code may just not initialize the new member. This can be utilized to an advantage: new data members can inserted into save files during patching manually, perhaps by introducing default values (or by more intelligent means).
This format also allows the save files to be easily manipulated or modified by hand; the order in which the data members doesn't really have much to do with the validity of the serialization routine. Each member is looked up and initialized independently. This might be a nicety that adds a little extra robustness.
All of this can be achieved through some form of type introspection. You'll want to be able to query a data member by string lookup, and be able to tell what the actual type of data the data member is. This can be achieved in C++ using a form of custom introspection, and other languages might have introspection facilities built in.