Serde and serde_json 0.9.0 released


In the six months since Serde 0.8.0 we have learned a lot about how people use Serde, how people wish they could use Serde, and the common failure modes when people try to use Serde.

The primary focus of 0.9.0 is on using Rust's type system to enforce contracts that were implicit and fragile in previous Serde versions. All four of the Serialize, Deserialize, Serializer, and Deserializer traits have been redesigned to use ownership as a way to enforce that implementations are correct.

Another exciting improvement is a redesigned error-reporting API that produces substantially more helpful error messages. The new API supports no_std as a first-class citizen, meaning no_std users are now able to receive the same helpful messages as std. Speaking of no_std, #[derive(Serialize, Deserialize)] now works within no_std crates.

Serde 0.9.0 is a huge release compared to previous ones. Thank you to dozens of people who have contributed improvements, opened issues, provided help to others, or expressed their frustrations with Serde in a constructive way. We appreciate your continued support as we pin down what a 1.0.0 data structure serialization library for Rust should look like.

This release includes serde_codegen for Rust 1.14 and earlier users. With Macros 1.1 custom derives being stabilized next week in Rust 1.15, we will not be releasing any further 0.9.x versions of serde_codegen. Please migrate to serde_derive at your convenience. See the manual for steps.

There are no silent breaking changes in this release. As you work to update your libraries, the compiler will tell you everything that needs to be changed.

Users of serde_json should read also the serde_json 0.9.0 release notes. This is a great release for serde_json, including a powerful new json! macro.

Breaking changes

  • The signature of Serialize::serialize has changed.

    - fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
    -     where S: Serializer;
    + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    +     where S: Serializer;
    

    Together with the Serializer changes in this release, the new signature enforces that the implementation makes exactly one serializer method call. In previous Serde versions, an incorrect Serialize implementation could invoke the serializer not at all or more than once, resulting in a correctly-written serializer producing syntactically invalid output.

  • The signature of Deserialize::deserialize has changed.

    - fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
    -     where D: Deserializer;
    + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    +     where D: Deserializer;
    

    Same reason as the Serialize change.

  • The Serializer comes with an associated type S::Ok.

    All Serializer methods return this type and it is propagated by the Serialize trait as you can see above. The various Serializer methods have changed like this:

    - fn serialize_bool(&mut self, v: bool) -> Result<(), Self::Error>;
    + fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error>;
    

    The change from &mut self to self is how the Serialize trait enforces at most one call to a serializer method, and the associated type S::Ok is how the Serialize trait enforces at least one call to a serializer method. Together these changes enforce a correct Serialize implementation.

    Most serializers that produce binary or text data as output or write to a stream will want to set Ok = (). In some cases serializers that build in-memory data structures as output may benefit from using a different Ok type. For example serde_json provides a Serializer that turns a type T into serde_json::Value (this is distinct from the main serde_json Serializer that produces JSON text). This Serializer is vastly simplified by using Ok = serde_json::Value compared to its previous implementation.

    The change from &mut self to self means that many Serializers will want to change their implementation from impl Serializer for SomeThing to impl<'a> Serializer for &'a mut SomeThing.

  • The serialization scheme for compound types has changed.

    This does not affect types that use #[derive(Serialize)]. It affects types that provide custom implementations of Serialize and serialize into one of the compound types: sequence, tuple, tuple struct, tuple variant, map, struct, or struct variant.

    For example to serialize a sequence type (like a Vec<T>):

    - let mut seq_state = serializer.serialize_seq(Some(self.len()))?;
    - for element in self {
    -     serializer.serialize_seq_elt(&mut seq_state, element)?;
    - }
    - serializer.serialize_seq_end(seq_state)
    + let mut seq = serializer.serialize_seq(Some(self.len()))?;
    + for element in self {
    +     seq.serialize_element(element)?;
    + }
    + seq.end()
    

    The new scheme is more concise and uses ownership to enforce a correct implementation. It is impossible to forget to call end() because you cannot get an S::Ok instance otherwise. Previously, forgetting to call serialize_seq_end would often result in broken output from the serializer.

  • The pointer-sized usize and isize types are no longer part of the Serde data model.

    Rust's usize and isize types are still supported but will be serialized as u64 and i64 from the perspective of a Serializer and Deserializer implementation.

  • The ser::Error and de::Error traits have been redesigned.

    The new design is able to produce substantially more helpful error messages and supports no_std as a first-class citizen without sacrificing usability for std users.

    - Invalid type. Expected `I64`
    + invalid type: integer `99`, expected a string
    
    - Unknown variant `X`
    + unknown variant `X`, expected one of `A`, `B`, `C`
    
    - Invalid length: 1
    + invalid length 1, expected a tuple of 2 elements
    

    Please see the API documentation for details.

  • A new stateful deserialization API.

    A DeserializeSeed trait has been added to handle cases where deserialization needs to depend on some pre-existing piece of state (the "seed"). One possible application of this API is for deserialization that writes into an existing previously allocated buffer rather than allocating a fresh buffer for every element. The existing buffer can be used as a "seed" that the DeserializeSeed implementation can write into.

    This API dramatically simplifies serde-transcode and other streaming deserialization use cases, eliminating the need for unsafe code in many situations.

    Refer to the API documentation for a complete example.

  • The end() method has been removed from SeqVisitor and MapVisitor.

    This affects custom implementations of Deserialize that deserialize from a sequence or map type. The end() method was too easy to forget to call and it was ambiguous whether it needed to be called on various error codepaths.

  • The enum deserialization API has been simplified.

    This affects custom implementations of Deserialize that process enum types. The new enum API is now much more similar to the sequence and map deserialization APIs and some convoluted indirection has been cleaned up.

12 Likes