Crate serdapt

Source
Expand description

§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

  1. Define a type to represent the new adapter.
  2. Implement SerializeWith and DeserializeWith for this type. This allows adapter composability.
  3. Define serialize and deserialize inherent functions for this type, delegating to SerializeWith and DeserializeWith 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);

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.

Structs§

AddRef
Adapter to serialize values of type T with an adapter F that expects &T
Array
Adapter to customize how array items are serialized
ByteVec
Adapter for contiguous byte sequences that can be converted from Vec<u8>
Bytes
Adapter for contiguous byte sequences
Cell
Adapter for cell-like types
Codec
Adapter to pair a serialization adapter with a deserialization adapter
Cow
Cow adapter
Fold
Sequence adapter to fold over its items when deserializing
From
Adapter to deserialize using a From conversion
HumanOr
Adapter for custom serialization when the serialization format is human-readable
Id
Identity adapter
Into
Adapter to serialize using a From conversion
Map
Map adapter to customize how keys and values are serialized
MapAsSeq
Adapter to serialize a map as a sequence
Mutex
Adapter for Mutex
Option
Adapter for Option
Ptr
Adapter for pointer-like types to customize how the inner type is serialized
Range
Adapter for range-related types
Result
Adapter for Result
Reverse
Adapter for Reverse
RwLock
Adapter for RwLock
Seq
Sequence adapter to customize how items are serialized
SeqAsMap
Adapter to serialize a sequence of pairs as a map
Str
Adapter to serialize types using their Display and FromStr implementations
TryFrom
Adapter to deserialize using a TryFrom conversion
TryInto
Adapter to serialize using a TryFrom conversion
WithEncoding
Type bundling a value and how to (de)serialize it
Wrapping
Adapter for Wrapping

Traits§

DeserializeWith
Trait for types that can be used as deserialization adapters with #[serde(with = ...)]
SerializeWith
Trait for types that can be used as serialization adapters with #[serde(with = ...)]

Type Aliases§

Convert
Adapter to serialize through a From conversion
RefConvert
Adapter to serialize using From on a borrow of the source value
RefTryConvert
Adapter to serialize using TryFrom on a borrow of the source value
TryConvert
Adapter to serialize through a TryFrom conversion