When software are developped and used at the same time the data formats may change from one version to another and persisting data may be produced by a specific version and loaded by another version.
Serde version provide a versioning feature for serde for the main use cases.
Note: requires the specialization feature.
We aim at solving the case were a type or a set of types in a deserializer's data needs to be upgraded to their latest format. This is the case when a mandatory property was added or removed, or an existing property changed.
Note: There already is support for added optional properties in serde.
(Use the default
feature of serde)
Example:
Let's have a file containing these structure with those version number:
A: 1, B: 1, C: 2
and the current version numbers are: A: 3, B: 2, C: 4
.
Then in latest code version, we have the former data structures versions,
let's call them: Av1
, Av2
, Bv1
, Cv1
, Cv2
, Cv3
.
Deserializing, whenever a structure A
, B
or C
is ran into,
then it is loaded with the appropriate format (in our case it will be Av1
, Bv1
and Cv2
)
and then converted to A
, B
or C
using the From trait.
This is based on types that can be upgraded individually. Types that needs to be upgraded together is way more complex to handle and usually relies on domain specific deserializer.
So, these data format should be handle with specific Deserialize
traits implementations.
deserialize_in_place
is not supportedDeserializing in place with versioning support is way more complicated, so we don't deal with this in this crate.
deserialize_with
callbackYou must take care of the versioning in your callback
There is no use case where versioning tuples and the unit type is useful.
To describe the previous versions of a type, we use the #[versions(...)]
attribute along with
the DeserializeVersioned
trait.
Authoring example: ```rust // Version 1 of struct A // It must implement Deserialize, so it can be loaded by serde
// It must be identified by A during deserialization
struct Av1 { a: u8 }
// Current version of struct A // It must implement Deserialize and DeserializeVersioned
// We use the versions attribute to define the previous versions
// So, Version n°1 of A is Av1, Versions n°2 (current) of A is A struct A { // We moved a property b: u8 }
// A must implement From for all previous type, so we implement From
To perform the deserialization with the versioning support, we need to do two steps:
1. Get the VersionMap
which holds the version number to use per type
1. Call the deserialize_versioned
method with the VersionMap
Note: The id used to find the version number of a type during deserialization is the deserialization name of the type.
Execution example: ```rust
struct AInMap { a: A, }
fn main() { // Use ron as data format for this example use ron; use serde_version::DeserializeVersioned;
// First get a header
// Here, we use the version 1 of A
let versions: serdeversion::VersionMap = ron::de::fromstr(r#"{ "A": 1 }"#).unwrap();
// Let's deserialize some values
// Deserialize directly A let mut deserializer = ron::de::Deserializer::fromstr(r#"A(a: 1)"#).unwrap(); let value = A::deserializeversioned(&mut deserializer, &versions); assert_eq!(value, A { b: 1 });
// Deserialize A contained in a struct property
let mut deserializer = ron::de::Deserializer::fromstr(r#"AInMap(a: A(a: 2))"#);
// Note: All types implementing Deserialize
will also implement DeserializeVersioned
let value = AInMap::deserializeversioned(&mut deserializer, &versions);
assert_eq!(value.a, A { b: 2});
}
```
VersionedDeserializer
Under the hood, deserialize_version
wraps the provided deserializer with
the VersionedDeserializer
to support the versioning.
A version group is a set of types with their associated version. It is often easier to use a version number for multiple types together.
To do so, you can define a VersionMap
on a lazy_static variable
and use it for the deserialization.
See example versioned_groups
.