Crates.io | serdapt |
lib.rs | serdapt |
version | 0.1.0 |
source | src |
created_at | 2024-08-28 23:25:25.698382 |
updated_at | 2024-08-28 23:25:25.698382 |
description | Composable adapters for `#[serde(with = ...)]` attribute |
homepage | https://github.com/stephaneyfx/serdapt |
repository | https://github.com/stephaneyfx/serdapt.git |
max_upload_size | |
id | 1355447 |
size | 108,885 |
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
.
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>
.
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);
SerializeWith
] and [DeserializeWith
] for this type. This allows adapter
composability.serialize
and deserialize
inherent functions for this type, delegating to
[SerializeWith
] and [DeserializeWith
] respectively. These are the functions the
serde-generated code calls.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);
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);
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.All contributions shall be licensed under the 0BSD license.