Tools for supporting version migration of serialization format

I'm working on a game that uses Serde and Bincode for serialization. I have the 'saved game' in a single GameState struct which I save to disk using Bincode:

#[derive(Serialize, Deserialize)]
pub struct GameState {
  // Lots of stuff

The problem I have is that whenever the game logic changes, I often end up needing to add new fields somewhere within the game state, which in turn causes deserialization to fail, and I need to write annoying code to make GameState1 and convert it to GameState2, even if I just added a single new boolean to some leaf node struct.

Are there any tools/crates that make this process less painful? I understand that for more complex changes in the structure I'll need to do manual conversion but I'd love to be able to say "if this field doesn't exist in the input, just use Default::default() to populate it".

(happy to use something other than serde/bincode if that helps)

Bincode is a schemaless serialization format. This means that the type you deserialize it into needs to exactly match the type you serialize it from. For schemaless serialization formats there is no other way than to keep a copy of the old type to deserialize into. There is simply no way to find out that which field is missing, or even if a field is missing in the first place. For something like json which does have a schema embedded you can use #[serde(default)] to tell serde to use the result of Default::default() in case a field is missing. Or #[serde(default = "some_function")] to call some_function() to get the default value.

1 Like

You might just want to properly version your type, like this:

#[derive(Serialize, Deserialize)]
pub enum GameState {
    // ...

I suggest also putting an explicit version at the start of the file. Automatic enum in Serde requires there to be a hard parse error, but for bincode it can happen that V2 deserializes as garbage data in V1 without detecting any error.