Path Serialization and Deserialization
The path_serde
crate introduces essential interfaces for serializing and deserializing specific parts of types by providing paths to their internals.
This functionality is mainly used by Communication to serialize data and provide it to connected debugging applications.
Traits
The crate provides three distinct traits: PathSerialize
, PathDeserialize
, and PathIntrospect
.
PathSerialize
The PathSerialize trait enables the serialization of specific parts of types by accepting a path to the desired internal data. This is particularly useful when only certain portions of a data structure need to be serialized.
trait PathSerialize {
fn serialize_path<S>(&self, path: &str, serializer: S) -> Result<S::Ok, Error<S::Error>>
where
S: Serializer;
}
For instance a user is only interested in the position angle value of the ankle pitch joint, a serialization of the path Control.main.sensor_data.positions.ankle_pitch
results in serializing only this specific float value.
PathDeserialize
Conversely, the PathDeserialize
trait facilitates the deserialization of data into types given a specified path.
trait PathDeserialize {
fn deserialize_path<'de, D>(
&mut self,
path: &str,
deserializer: D,
) -> Result<(), Error<D::Error>>
where
D: Deserializer<'de>;
}
This functionality is used when changing only parts of parameters.
PathIntrospect
The PathIntrospect
trait enables type introspection, allowing the user to generate a set of available paths to fields of a type.
This functionality is valuable for dynamically exploring the structure of data types and determining the paths that can be utilized for serialization and deserialization.
For instance, tooling may use these paths to autocomplete available paths when subscribing data from the robot.
Macro
path_serde
also provides derive macros, automatically generating the implementation of the three traits.
The source of an annotated type is analyzed and implementation is generated for each field, delegating the call to sub-types.
Attributes
Types and fields can be additionally annotated with attributes to modify code generation.
Each attribute is prefixed with #[path_serde(<...>)
to identify attributes to the path_serde
macros.
We define the following attributes:
Container: bound
This attribute is attached to a container type and defines generic where bounds to the implementation.
#[derive(Serialize, PathSerialize)]
#[path_serde(bound = T: PathSerialize + Serialize)]
struct MyStruct<T> {
foo: T,
}
Container: add_leaf
The add_leaf
attribute adds an additional leaf to the children of a type by specifying a leaf name and a type.
This type is required to implement a TryFrom<Self>
to generate the data for this field when requested.
Additionally, this type must be serializable or deserializable.
#[derive(Serialize, PathSerialize)]
#[path_serde(add_leaf(bar: MyIntoType)]
struct MyStruct {
foo: i32,
}
Field: leaf
This attributes tags a field to be a leaf of the tree. That means, it is not expected to have further children, and path delegation ends at this field.
#[derive(Serialize, PathSerialize)]
pub struct MultivariateNormalDistribution {
pub mean: f32,
#[path_serde(leaf)]
pub covariance: FancyType,
}
Field: skip
This attributes tags a field to be skipped for the implementation. It is neither considered for (de-)serialization, nor included in the available paths.
#[derive(Serialize, PathSerialize)]
pub struct MultivariateNormalDistribution {
pub mean: f32,
#[path_serde(skip)]
pub covariance: FancyType,
}
Example Usage
#[derive(PathSerialize, PathDeserialize, PathIntrospect)]
struct ExampleStruct {
foo: u32,
bar: String,
}
fn main() {
let example = ExampleStruct {
foo: 42,
bar: String::from("example"),
};
// Serialize data using path
let serialized_data = example.serialize_path("foo", /* serializer */);
// Deserialize data from a specified path
let deserialized_data = example.deserialize_path("bar", /* deserializer */);
// Generate a set of all available paths within ExampleStruct
let available_paths = ExampleStruct::get_fields();
}