diff --git a/serde_with/src/de/impls.rs b/serde_with/src/de/impls.rs index 61eaf5ca..7ea590f3 100644 --- a/serde_with/src/de/impls.rs +++ b/serde_with/src/de/impls.rs @@ -1719,6 +1719,35 @@ where } } +impl<'de, T, U> DeserializeAs<'de, T> for FromIntoRef +where + U: Into, + U: Deserialize<'de>, +{ + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(U::deserialize(deserializer)?.into()) + } +} + +impl<'de, T, U> DeserializeAs<'de, T> for TryFromIntoRef +where + U: TryInto, + >::Error: Display, + U: Deserialize<'de>, +{ + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + U::deserialize(deserializer)? + .try_into() + .map_err(DeError::custom) + } +} + #[cfg(feature = "alloc")] impl<'de> DeserializeAs<'de, Cow<'de, str>> for BorrowCow { fn deserialize_as(deserializer: D) -> Result, D::Error> diff --git a/serde_with/src/lib.rs b/serde_with/src/lib.rs index 1a30b0d4..6dba0194 100644 --- a/serde_with/src/lib.rs +++ b/serde_with/src/lib.rs @@ -1825,6 +1825,87 @@ pub struct PickFirst(PhantomData); /// ``` pub struct FromInto(PhantomData); +/// Serialize a reference value by converting to/from a proxy type with serde support. +/// +/// This adapter serializes a type `O` by converting it into a second type `T` and serializing `T`. +/// Deserializing works analogue, by deserializing a `T` and then converting into `O`. +/// +/// ```rust +/// # #[cfg(FALSE)] { +/// struct S { +/// #[serde_as(as = "FromIntoRef")] +/// value: O, +/// } +/// # } +/// ``` +/// +/// For serialization `O` needs to be `for<'a> &'a O: Into`. +/// For deserialization the opposite `T: Into` is required. +/// +/// **Note**: [`TryFromIntoRef`] is the more generalized version of this adapter which uses the [`TryInto`](std::convert::TryInto) trait instead. +/// +/// # Example +/// +/// ```rust +/// # #[cfg(feature = "macros")] { +/// # use serde::{Deserialize, Serialize}; +/// # use serde_json::json; +/// # use serde_with::{serde_as, FromIntoRef}; +/// # +/// #[derive(Debug, PartialEq)] +/// struct Rgb { +/// red: u8, +/// green: u8, +/// blue: u8, +/// } +/// +/// # /* +/// impl From<(u8, u8, u8)> for Rgb { ... } +/// impl From for (u8, u8, u8) { ... } +/// # */ +/// # +/// # impl From<(u8, u8, u8)> for Rgb { +/// # fn from(v: (u8, u8, u8)) -> Self { +/// # Rgb { +/// # red: v.0, +/// # green: v.1, +/// # blue: v.2, +/// # } +/// # } +/// # } +/// # +/// # impl<'a> From<&'a Rgb> for (u8, u8, u8) { +/// # fn from(v: &'a Rgb) -> Self { +/// # (v.red, v.green, v.blue) +/// # } +/// # } +/// +/// #[serde_as] +/// # #[derive(Debug, PartialEq)] +/// #[derive(Deserialize, Serialize)] +/// struct Color { +/// #[serde_as(as = "FromIntoRef<(u8, u8, u8)>")] +/// rgb: Rgb, +/// } +/// let color = Color { +/// rgb: Rgb { +/// red: 128, +/// green: 64, +/// blue: 32, +/// }, +/// }; +/// +/// // Define our expected JSON form +/// let j = json!({ +/// "rgb": [128, 64, 32], +/// }); +/// // Ensure serialization and deserialization produce the expected results +/// assert_eq!(j, serde_json::to_value(&color).unwrap()); +/// assert_eq!(color, serde_json::from_value(j).unwrap()); +/// # } +/// ``` +pub struct FromIntoRef(PhantomData); + /// Serialize value by converting to/from a proxy type with serde support. /// /// This adapter serializes a type `O` by converting it into a second type `T` and serializing `T`. @@ -1915,6 +1996,95 @@ pub struct FromInto(PhantomData); /// ``` pub struct TryFromInto(PhantomData); +/// Serialize a reference value by converting to/from a proxy type with serde support. +/// +/// This adapter serializes a type `O` by converting it into a second type `T` and serializing `T`. +/// Deserializing works analogue, by deserializing a `T` and then converting into `O`. +/// +/// ```rust +/// # #[cfg(FALSE)] { +/// struct S { +/// #[serde_as(as = "TryFromIntoRef")] +/// value: O, +/// } +/// # } +/// ``` +/// +/// For serialization `O` needs to be `for<'a> &'a O: TryInto`. +/// For deserialization the opposite `T: TryInto` is required. +/// In both cases the `TryInto::Error` type must implement [`Display`](std::fmt::Display). +/// +/// **Note**: [`FromIntoRef`] is the more specialized version of this adapter which uses the infallible [`Into`] trait instead. +/// [`TryFromIntoRef`] is strictly more general and can also be used where [`FromIntoRef`] is applicable. +/// The example shows a use case, when only the deserialization behavior is fallible, but not serializing. +/// +/// # Example +/// +/// ```rust +/// # #[cfg(feature = "macros")] { +/// # use serde::{Deserialize, Serialize}; +/// # use serde_json::json; +/// # use serde_with::{serde_as, TryFromIntoRef}; +/// # use std::convert::TryFrom; +/// # +/// #[derive(Debug, PartialEq)] +/// enum Boollike { +/// True, +/// False, +/// } +/// +/// # /* +/// impl From for u8 { ... } +/// # */ +/// # +/// impl TryFrom for Boollike { +/// type Error = String; +/// fn try_from(v: u8) -> Result { +/// match v { +/// 0 => Ok(Boollike::False), +/// 1 => Ok(Boollike::True), +/// _ => Err(format!("Boolikes can only be constructed from 0 or 1 but found {}", v)) +/// } +/// } +/// } +/// # +/// # impl<'a> From<&'a Boollike> for u8 { +/// # fn from(v: &'a Boollike) -> Self { +/// # match v { +/// # Boollike::True => 1, +/// # Boollike::False => 0, +/// # } +/// # } +/// # } +/// +/// #[serde_as] +/// # #[derive(Debug, PartialEq)] +/// #[derive(Deserialize, Serialize)] +/// struct Data { +/// #[serde_as(as = "TryFromIntoRef")] +/// b: Boollike, +/// } +/// let data = Data { +/// b: Boollike::True, +/// }; +/// +/// // Define our expected JSON form +/// let j = json!({ +/// "b": 1, +/// }); +/// // Ensure serialization and deserialization produce the expected results +/// assert_eq!(j, serde_json::to_value(&data).unwrap()); +/// assert_eq!(data, serde_json::from_value(j).unwrap()); +/// +/// // Numbers besides 0 or 1 should be an error +/// let j = json!({ +/// "b": 2, +/// }); +/// assert_eq!("Boolikes can only be constructed from 0 or 1 but found 2", serde_json::from_value::(j).unwrap_err().to_string()); +/// # } +/// ``` +pub struct TryFromIntoRef(PhantomData); + /// Borrow `Cow` data during deserialization when possible. /// /// The types `Cow<'a, [u8]>`, `Cow<'a, [u8; N]>`, and `Cow<'a, str>` can borrow from the input data during deserialization. diff --git a/serde_with/src/ser/impls.rs b/serde_with/src/ser/impls.rs index 0075a8b8..e9e10fda 100644 --- a/serde_with/src/ser/impls.rs +++ b/serde_with/src/ser/impls.rs @@ -877,6 +877,36 @@ where } } +impl SerializeAs for FromIntoRef +where + for<'a> &'a T: Into, + U: Serialize, +{ + fn serialize_as(source: &T, serializer: S) -> Result + where + S: Serializer, + { + source.into().serialize(serializer) + } +} + +impl SerializeAs for TryFromIntoRef +where + for<'a> &'a T: TryInto, + for<'a> <&'a T as TryInto>::Error: Display, + U: Serialize, +{ + fn serialize_as(source: &T, serializer: S) -> Result + where + S: Serializer, + { + source + .try_into() + .map_err(S::Error::custom)? + .serialize(serializer) + } +} + #[cfg(feature = "alloc")] impl<'a> SerializeAs> for BorrowCow { fn serialize_as(source: &Cow<'a, str>, serializer: S) -> Result diff --git a/serde_with/tests/serde_as/fromintoref.rs b/serde_with/tests/serde_as/fromintoref.rs new file mode 100644 index 00000000..430986b9 --- /dev/null +++ b/serde_with/tests/serde_as/fromintoref.rs @@ -0,0 +1,187 @@ +use super::*; +use core::convert::TryFrom; +use serde_with::{FromIntoRef, TryFromIntoRef}; + +#[derive(Debug, PartialEq)] +enum IntoSerializable { + A, + B, + C, +} + +impl<'a> From<&'a IntoSerializable> for String { + fn from(value: &'a IntoSerializable) -> Self { + match value { + IntoSerializable::A => "String A", + IntoSerializable::B => "Some other value", + IntoSerializable::C => "Looks like 123", + } + .to_string() + } +} + +#[derive(Debug, PartialEq)] +enum FromDeserializable { + Zero, + Odd(u32), + Even(u32), +} + +impl From for FromDeserializable { + fn from(value: u32) -> Self { + match value { + 0 => FromDeserializable::Zero, + e if e % 2 == 0 => FromDeserializable::Even(e), + o => FromDeserializable::Odd(o), + } + } +} + +#[derive(Debug, PartialEq)] +enum LikeBool { + Trueish, + Falseisch, +} + +impl From for LikeBool { + fn from(b: bool) -> Self { + if b { + LikeBool::Trueish + } else { + LikeBool::Falseisch + } + } +} + +impl<'a> From<&'a LikeBool> for bool { + fn from(lb: &'a LikeBool) -> Self { + match lb { + LikeBool::Trueish => true, + LikeBool::Falseisch => false, + } + } +} + +#[test] +fn test_frominto_ser() { + #[serde_as] + #[derive(Debug, PartialEq, Serialize)] + struct S(#[serde_as(serialize_as = "FromIntoRef")] IntoSerializable); + + check_serialization(S(IntoSerializable::A), expect![[r#""String A""#]]); + check_serialization(S(IntoSerializable::B), expect![[r#""Some other value""#]]); + check_serialization(S(IntoSerializable::C), expect![[r#""Looks like 123""#]]); +} + +#[test] +fn test_tryfrominto_ser() { + #[serde_as] + #[derive(Debug, PartialEq, Serialize)] + struct S(#[serde_as(serialize_as = "TryFromIntoRef")] IntoSerializable); + + check_serialization(S(IntoSerializable::A), expect![[r#""String A""#]]); + check_serialization(S(IntoSerializable::B), expect![[r#""Some other value""#]]); + check_serialization(S(IntoSerializable::C), expect![[r#""Looks like 123""#]]); +} + +#[test] +fn test_frominto_de() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize)] + struct S(#[serde_as(deserialize_as = "FromIntoRef")] FromDeserializable); + + check_deserialization(S(FromDeserializable::Zero), "0"); + check_deserialization(S(FromDeserializable::Odd(1)), "1"); + check_deserialization(S(FromDeserializable::Odd(101)), "101"); + check_deserialization(S(FromDeserializable::Even(2)), "2"); + check_deserialization(S(FromDeserializable::Even(202)), "202"); +} + +#[test] +fn test_tryfrominto_de() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize)] + struct S(#[serde_as(deserialize_as = "TryFromIntoRef")] FromDeserializable); + + check_deserialization(S(FromDeserializable::Zero), "0"); + check_deserialization(S(FromDeserializable::Odd(1)), "1"); + check_deserialization(S(FromDeserializable::Odd(101)), "101"); + check_deserialization(S(FromDeserializable::Even(2)), "2"); + check_deserialization(S(FromDeserializable::Even(202)), "202"); +} + +#[test] +fn test_frominto_de_and_ser() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde_as(as = "FromIntoRef")] LikeBool); + + is_equal(S(LikeBool::Trueish), expect![[r#"true"#]]); + is_equal(S(LikeBool::Falseisch), expect![[r#"false"#]]); +} + +#[test] +fn test_tryfrominto_de_and_ser() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde_as(as = "TryFromIntoRef")] LikeBool); + + is_equal(S(LikeBool::Trueish), expect![[r#"true"#]]); + is_equal(S(LikeBool::Falseisch), expect![[r#"false"#]]); +} + +#[derive(Debug, PartialEq)] +enum TryIntoSerializable { + Works, + Fails, +} + +impl<'a> TryFrom<&'a TryIntoSerializable> for String { + type Error = &'static str; + + fn try_from(value: &'a TryIntoSerializable) -> Result { + match value { + TryIntoSerializable::Works => Ok("Works".to_string()), + TryIntoSerializable::Fails => Err("Fails cannot be turned into String"), + } + } +} + +#[derive(Debug, PartialEq)] +enum TryFromDeserializable { + Zero, +} + +impl TryFrom for TryFromDeserializable { + type Error = &'static str; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(TryFromDeserializable::Zero), + _ => Err("Number is not zero"), + } + } +} + +#[test] +fn test_tryfrominto_ser_with_error() { + #[serde_as] + #[derive(Debug, PartialEq, Serialize)] + struct S(#[serde_as(serialize_as = "TryFromIntoRef")] TryIntoSerializable); + + check_serialization(S(TryIntoSerializable::Works), expect![[r#""Works""#]]); + check_error_serialization( + S(TryIntoSerializable::Fails), + expect![[r#"Fails cannot be turned into String"#]], + ); +} + +#[test] +fn test_tryfrominto_de_with_error() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize)] + struct S(#[serde_as(deserialize_as = "TryFromIntoRef")] TryFromDeserializable); + + check_deserialization(S(TryFromDeserializable::Zero), "0"); + check_error_deserialization::("1", expect![[r#"Number is not zero"#]]); +} diff --git a/serde_with/tests/serde_as/lib.rs b/serde_with/tests/serde_as/lib.rs index d9e0d3db..23e1ba85 100644 --- a/serde_with/tests/serde_as/lib.rs +++ b/serde_with/tests/serde_as/lib.rs @@ -12,6 +12,7 @@ mod collections; mod default_on; mod enum_map; mod frominto; +mod fromintoref; mod key_value_map; mod map_tuple_list; mod pickfirst;