This problem sounds like a metaprogramming problem.
I know libraries are not that encouraged in this community. But I would suggest you take a look at Protobuf:
https://developers.google.com/protocol-buffers/
It is a simple library that tackles exactly your problem. Adding new fields to existing data structures does not break the serialization process. The implementation is quite simple on an abstract level. Every member of a struct is assigned a unique number in that struct. This number is used as a key when loading or storing data. If you add a new member, old data can still be deserialized. In that case the new member will be zero-initialized. If you remove a member, this member will be ignored while parsing old data.
You can use that same strategy to implement your own serialization. Keep in mind that every field must have a unique number. That means even if you remove a member you cannot use that number again.
If you want to do metaprogramming yourself, you could use a format like this:
| struct data SERIALIZE()
{
int ID; ID(1)
//float Unused; ID(2)
float X; ID(3)
float Y; ID(4)
};
|
The SERIALIZE() marks the struct as serializable. The ID(num) sets the unique number for the members of the struct. In this example the float member "Unused" was commented out to show that you cannot use the ID 2 anymore after it has been used by a previous version of the struct.
As you may have noticed this aproach does not require a version number for the whole struct. As long as you do not reuse field numbers and zero-initialisation is okay for not set members this method should be both backwards and forwards compatible.