1 unstable release
0.1.0 | Aug 28, 2024 |
---|
#438 in Encoding
Used in serdapt-base64
110KB
2.5K
SLoC
Overview
Tools to build composable adapters for #[serde(with = ...)]
.
serde
allows customizing how fields are serialized when deriving Serialize
and Deserialize
thanks to the #[serde(with = "path")]
attribute. With such an attribute, path::serialize
and path::deserialize
are the functions used for serialization. By using a type for path
,
composable serialization adapters can be defined, e.g. to customize how items in a container
are serialized.
These adapters can also simplify implementing Serialize
and Deserialize
.
Apply adapter
An adapter is applied by specifying the adapter path in #[serde(with = "...")]
. The path
needs to be suitable as a prefix for functions, i.e. path::serialize
and path::deserialize
.
This means the turbofish is needed for generic adapters, e.g. Outer::<Inner>
.
Example
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct Foo {
#[serde(with = "serdapt::Seq::<serdapt::Str>")]
xs: Vec<i32>,
}
let foo = Foo { xs: vec![3, 4] };
let v = serde_json::to_value(&foo).unwrap();
assert_eq!(v, serde_json::json!({ "xs": ["3", "4"] }));
assert_eq!(serde_json::from_value::<Foo>(v).unwrap(), foo);
Define serialization adapter
- Define a type to represent the new adapter.
- Implement
SerializeWith
andDeserializeWith
for this type. This allows adapter composability. - Define
serialize
anddeserialize
inherent functions for this type, delegating toSerializeWith
andDeserializeWith
respectively. These are the functions the serde-generated code calls.
Simple adapter example
use serdapt::{DeserializeWith, SerializeWith};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::json;
#[derive(Deserialize, Serialize)]
struct Point {
x: i32,
y: i32,
}
struct Coords;
impl Coords {
fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: ?Sized,
S: Serializer,
Self: SerializeWith<T>,
{
Self::serialize_with(value, serializer)
}
fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
Self: DeserializeWith<'de, T>,
{
Self::deserialize_with(deserializer)
}
}
impl SerializeWith<(i32, i32)> for Coords {
fn serialize_with<S>(&(x, y): &(i32, i32), serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Serialize::serialize(&Point { x, y }, serializer)
}
}
impl<'de> DeserializeWith<'de, (i32, i32)> for Coords {
fn deserialize_with<D>(deserializer: D) -> Result<(i32, i32), D::Error>
where
D: Deserializer<'de>,
{
let Point { x, y } = Deserialize::deserialize(deserializer)?;
Ok((x, y))
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct Shape(#[serde(with = "serdapt::Seq::<Coords>")] Vec<(i32, i32)>);
let original = Shape(vec![(1, 2), (3, 4)]);
let serialized = serde_json::to_value(&original).unwrap();
assert_eq!(serialized, json!([{ "x": 1, "y": 2 }, { "x": 3, "y": 4 }]));
let deserialized = serde_json::from_value::<Shape>(serialized).unwrap();
assert_eq!(deserialized, original);
Generic adapter example
use core::marker::PhantomData;
use serdapt::{DeserializeWith, SerializeWith, WithEncoding};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::json;
#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct Point<T> {
x: T,
y: T,
}
struct Coords<F>(PhantomData<F>);
impl<F> Coords<F> {
fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: ?Sized,
S: Serializer,
Self: SerializeWith<T>,
{
Self::serialize_with(value, serializer)
}
fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
Self: DeserializeWith<'de, T>,
{
Self::deserialize_with(deserializer)
}
}
impl<F, T> SerializeWith<Point<T>> for Coords<F>
where
F: SerializeWith<T>,
{
fn serialize_with<S>(Point { x, y }: &Point<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let p: Point<WithEncoding<&F, &T>> = Point {
x: x.into(),
y: y.into()
};
Serialize::serialize(&p, serializer)
}
}
impl<'de, F, T> DeserializeWith<'de, Point<T>> for Coords<F>
where
F: DeserializeWith<'de, T>,
{
fn deserialize_with<D>(deserializer: D) -> Result<Point<T>, D::Error>
where
D: Deserializer<'de>,
{
let p: Point<WithEncoding<F, T>> = Deserialize::deserialize(deserializer)?;
Ok(Point {
x: p.x.into_inner(),
y: p.y.into_inner(),
})
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct Shape(
#[serde(with = "serdapt::Seq::<Coords<serdapt::Str>>")] Vec<Point<i32>>,
);
let original = Shape(vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]);
let serialized = serde_json::to_value(&original).unwrap();
assert_eq!(serialized, json!([{ "x": "1", "y": "2" }, { "x": "3", "y": "4" }]));
let deserialized = serde_json::from_value::<Shape>(serialized).unwrap();
assert_eq!(deserialized, original);
Related project
serde_with
allows the same composability with the help
of an additional proc-macro, though it is also possible to use #[serde(with = ...)]
directly.
Some key differences are:
serdapt
is simpler and does not need any additional proc-macro, giving up on any ergonomics such a macro provides.- It avoids a macro ordering issue that can lead to generated serialization code not using the requested adapter despite a sucessful compilation.
- It works seamlessly with conditional compilation.
- It is limited to supporting types in the standard library, with support for third-party types delegated to other crates, which solves dependency issues.
Contribute
All contributions shall be licensed under the 0BSD license.
Dependencies
~105–330KB