Если формат данных только дополняется то решение элементарное. Скажем у вас в проекте есть 2 класса реализующих чтение и сохранение данных. С каждой версией у вас просто появляются новые данные и старые точно не меняются. Добавляете(если не сделали уже) в каждом файле номер версии. В программе с каждым нововведением просто делаете еще пару классов чтения\записи под новую версию. Потом просто при открытие файла смотреть версию и использовать нужный класс для чтения.
Другой вариант более тупой, поправить класс чтения\записи так чтобы он игнорировал неизвестные ему данные. Т.е. если вы откроете в старой версии программы файл от более новой версии то он просто проигнорирует неизвестные ему данные.
На практике видел реализацию очень интересную. Там было очень хитро устроено чтение. Правда проект на яве был.
Был класс чтения\записи файла, псевдокод:
class CReader{
public CReader(URL file);
void readData(){
someStructs;
}
void writeData(){
someStructs;
}
... другие методы
}
То была первая версия программы, потом выходит новая версия в которой появились некие новые данные и структуры но старые не изменялись. Псевдокод:
class CReader1 extends CReader{
@Override
void readData(){
super(); //Выполнить родительский метод
someNewStructs;
}
@Override
void writeData(){
super(); //Выполнить родительский метод
someNewStructs;
}
}
Т.е. принцип такой что в конечном счете все новые данные которые вводятся с новой версией программы всегда пишутся в конце файла. Файл прекрасно открывается в старых версиях программы и без каких либо ошибок, просто если проект хотят сохранить в старой версии и присутствуют данные которых не было в старых версиях то выводится предупреждение при сохранении файла о частичной потере информации. Решение до глупости простое и в тоже время гениальное.