Module rubedo::serde

source ·
Expand description

This module provides conversion utility functions for use with Serde.

This module attempts to consider the common use cases for (de)serialisation, and provide functions that are semantically appropriate for those use cases. The functions are intended to be used with the #[serde(serialize_with)] and #[serde(deserialize_with)] attributes.

Of course, as this module is an extension of standard Serde functionality, it does not attempt to reproduce what Serde already does by default. For instance, if a struct has a field of type u32, then Serde will already know how to serialise and deserialise that field. Equally, if a struct has a field that is an enum, then Serde is able to serialise and deserialise that field according to the available enum variants, and the chosen internal representation of the enum.

Where this module provides particular value is when an alternative serialised representation is required for certain struct members. For example, it is common to have an enum that naturally serialises to and from an integer, but also has a string representation. Or equally, it could be that the required serialised form is not the default. This module allows easy specification of alternative forms for serialisation and deserialisation, while working with the existing Serde derive macros.

As a general statement, the intention of this module is to provide this functionality for data types such as structs and enums, where the Serde derive macros would be used, and there is no obvious application for primitive types such as integers, floats, and booleans, or string types such as String and str.

§Naming conventions

Generally in Rust, the naming of functions carries semantic meaning:

  • to_ prefix: This implies a conversion that does not necessarily consume the original value. It’s often seen in methods that return a new value based on the original, without consuming the original.

  • into_ prefix: This indicates that the function consumes the original value and transforms it into another. It’s commonly used with Rust’s ownership system, signifying that the original value will no longer be usable after the conversion.

  • as_ prefix: This is typically used for cheap reference conversions that don’t involve any data processing. It suggests a view or representation of the original value, not a conversion or transformation.

§From and Into

The first case considered is general conversion using Into and From. In a situation where a type implements Into<T> and either From<T> or TryFrom<T>, then it seems natural and appropriate to be able to use those implementations for serialisation and deserialisation. Indeed, Serde does allow this, and it is possible to use the #[serde(into)], and #[serde(from)], and and #[serde(try_from)] attributes to specify the desired primary types. However, these apply at the container level, and there are no equivalent attributes for specifying the same behaviour at the field level. Instead, the #[serde(with)] attribute can be used, but this requires the implementation of a custom serialiser and/or deserialiser. That’s where this module comes in.

The into(), from(), and try_from() functions can be used to specify the desired behaviour at the field level, matching the behaviour of the Serde container-level attributes, without the need to implement custom serialisers and deserialisers. This allows for variations other than the default to be easily specified. Additionally, ease-of-use functions for String conversion are provided in the form of into_string(), from_string(), and try_from_string(). Note that these functions expect to work on a full String, not a str slice, due to their context.

The end result is that it becomes trivial to specify alternate conversions for any type that implements the common conversion traits.

§Display and ToString, and FromStr

Implementing Display for a type adds a free implementation of ToString as well, which provides the to_string() method. This is intended to be used for human-readable representations of a type, and provides a String copy of the converted type. This is not necessarily the same as the serialised representation, which is intended for machine-readable uses. However, for cases where the Display implementation is the same as the serialised representation, it is possible to use the to_string() function to provide the desired behaviour.

Notably, this is conceptually a subset of the Into<String> use case, as Into<String> is intended to be used for any type that can be converted to a String, and ToString does that as well, albeit with a different semantic purpose, and via copy versus consumption. Although it is advised to use the into_string() or as_str() functions instead (as appropriate), the to_string() Serde helper function is provided for completeness and for such cases where a Display implementation may exist and is the same as the serialised form, in which case it would be onerous to also implement another function just for the sake of it.

The other side of the ToString coin is FromStr, which provides the from_str() method. This is intended to be used for parsing a String into a type, and is the counterpart to to_string(). For this purpose, the from_str() function is provided, which is basically equivalent to from(), but for String types. In this way, it serves the same essential purpose as from_string(), but for types that implement FromStr instead of TryFrom<String>. That is the only difference, and the choice of which to use is entirely down to the implementation of the type in question.

§AsStr

The second case considered is representation using AsStr. The as_str() function is intended to be used with any type that implements the AsStr trait, which provides an as_str() method. This function is primarily intended to be used with enums, where it is common to have variants that naturally serialise to and from integers, but also have a string representation. In such cases, the enums will typically be created with static &str values for such representation, in which case it is desirable to use and propagate those actual values by reference instead of making unnecessary copies. This is the purpose of the as_str() function.

In keeping with Rust naming conventions and idioms, the concept of representation is considered to be distinct from the concept of conversion, with this function providing an unmodified, uncopied “view” onto a value provided by the type for this purpose.

Functions§

  • Returns a string representation of a type from a string slice.
  • Returns a type from a string or other serialised representation.
  • Converts an integer to a Decimal to 2 decimal places.
  • Converts an integer to a Decimal to 2 decimal places.
  • Returns a type from a string slice representation.
  • Returns a type from a string representation.
  • Returns a serialised representation of a type.
  • Returns string representation of a type.
  • Converts a Decimal to an integer to 2 decimal places.
  • Converts a Decimal to an integer to 2 decimal places.
  • Returns a string copy of a type.
  • Returns a type from a string or other serialised representation.
  • Converts an integer to a floating-point number to 1 decimal place.
  • Converts an integer to a floating-point number to 2 decimal places.
  • Converts an integer to a floating-point number to 3 decimal places.
  • Converts an integer to a floating-point number to 4 decimal places.
  • Converts an integer to a floating-point number with scale.
  • Returns a type from a string representation.
  • Converts a floating-point number to an integer to 1 decimal place.
  • Converts a floating-point number to an integer to 2 decimal places.
  • Converts a floating-point number to an integer to 3 decimal places.
  • Converts a floating-point number to an integer to 4 decimal places.
  • Converts a floating-point number to an integer with scale.