Repository: jonasbb/serde_with (Rust)

Issue:
Create two new derives: `DeserializeFromStr` and `SerializeDisplay`
They would derive `Deserialize` and `Serialize` but using the `Display`/`FromStr` traits for implementation.
This would be a nicer way to configure the de/serialization where the struct is defined, without needing a wrapper type and without specifying this per field.

This proposal was also made here https://github.com/serde-rs/serde/issues/553#issuecomment-433801616 and here https://github.com/serde-rs/serde/issues/908


Discussion:

Patch:
diff --git a/serde_with_macros/src/lib.rs b/serde_with_macros/src/lib.rs
index d6312e1c..4d294a67 100644
--- a/serde_with_macros/src/lib.rs
+++ b/serde_with_macros/src/lib.rs
@@ -43,8 +43,9 @@ use proc_macro2::Span;
 use quote::{quote, ToTokens};
 use std::str::FromStr;
 use syn::{
-    parse::Parser, punctuated::Pair, spanned::Spanned, Attribute, Error, Field, Fields,
-    GenericArgument, ItemEnum, ItemStruct, Meta, NestedMeta, Path, PathArguments, ReturnType, Type,
+    parse::Parser, punctuated::Pair, spanned::Spanned, Attribute, DeriveInput, Error, Field,
+    Fields, GenericArgument, ItemEnum, ItemStruct, Meta, NestedMeta, Path, PathArguments,
+    ReturnType, Type,
 };
 
 /// Apply function on every field of structs or enums
@@ -643,3 +644,166 @@ fn replace_infer_type_with_type(to_replace: Type, replacement: &Type) -> Type {
         type_ => type_,
     }
 }
+
+/// Deserialize value by using it's [`FromStr`] implementation
+///
+/// This is an alternative way to implement `Deserialize` for types which also implement [`FromStr`] by deserializing the type from string.
+/// Ensure that the struct/enum also implements [`FromStr`].
+/// If the implementation is missing you will get a error message like
+/// ```text
+/// error[E0277]: the trait bound `Struct: std::str::FromStr` is not satisfied
+/// ```
+/// Additionally, `FromStr::Err` **must** implement [`Display`] as otherwise you will see a rather unhelpful error message
+///
+/// Serialization with [`Display`] is available with the matching [`SerializeDisplay`] derive.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// use std::str::FromStr;
+///
+/// #[derive(DeserializeFromStr)]
+/// struct A {
+///     a: u32,
+///     b: bool,
+/// }
+///
+/// impl FromStr for A {
+///     type Err = String;
+///
+///     /// Parse a value like `123<>true`
+///     fn from_str(s: &str) -> Result<Self, Self::Err> {
+///         let mut parts = s.split("<>");
+///         let number = parts
+///             .next()
+///             .ok_or_else(|| "Missing first value".to_string())?
+///             .parse()
+///             .map_err(|err: ParseIntError| err.to_string())?;
+///         let bool = parts
+///             .next()
+///             .ok_or_else(|| "Missing second value".to_string())?
+///             .parse()
+///             .map_err(|err: ParseBoolError| err.to_string())?;
+///         Ok(Self { a: number, b: bool })
+///     }
+/// }
+///
+/// let a: A = serde_json::from_str("\"159<>true\"").unwrap();
+/// assert_eq!(A { a: 159, b: true }, a);
+/// ```
+///
+/// [`Display`]: std::fmt::Display
+/// [`FromStr`]: std::str::FromStr
+#[proc_macro_derive(DeserializeFromStr)]
+pub fn derive_deserialize_fromstr(item: TokenStream) -> TokenStream {
+    // Convert any error message into a nice compiler error
+    let res = match deserialize_fromstr(item) {
+        Ok(res) => res,
+        Err(err) => err.to_compile_error(),
+    };
+    TokenStream::from(res)
+}
+
+fn deserialize_fromstr(item: TokenStream) -> Result<proc_macro2::TokenStream, Error> {
+    let input = syn::parse::<DeriveInput>(item)?;
+    let ident = input.ident;
+    Ok(quote! {
+        #[automatically_derived]
+        impl<'de> serde::Deserialize<'de> for #ident
+        where
+            Self: std::str::FromStr,
+            <Self as FromStr>::Err: std::fmt::Display,
+        {
+            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+            where
+                D: serde::Deserializer<'de>,
+            {
+                struct Helper<S>(std::marker::PhantomData<S>);
+
+                impl<'de, S> serde::de::Visitor<'de> for Helper<S>
+                where
+                    S: FromStr,
+                    <S as std::str::FromStr>::Err: std::fmt::Display,
+                {
+                    type Value = S;
+
+                    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                        write!(formatter, "string")
+                    }
+
+                    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
+                    where
+                        E: serde::de::Error,
+                    {
+                        value.parse::<Self::Value>().map_err(serde::de::Error::custom)
+                    }
+                }
+
+                deserializer.deserialize_str(Helper(std::marker::PhantomData))
+            }
+        }
+    })
+}
+
+/// Serialize value by using it's [`Display`] implementation
+///
+/// This is an alternative way to implement `Serialize` for types which also implement [`Display`] by serializing the type as string.
+/// Ensure that the struct/enum also implements [`Display`].
+/// If the implementation is missing you will get a error message like
+/// ```text
+/// error[E0277]: `Struct` doesn't implement `std::fmt::Display`
+/// ```
+///
+/// Deserialization with [`FromStr`] is available with the matching [`DeserializeFromStr`] derive.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// use std::fmt;
+///
+/// #[derive(SerializeDisplay)]
+/// struct A {
+///     a: u32,
+///     b: bool,
+/// }
+///
+/// impl fmt::Display for A {
+///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+///         write!(f, "{}<>{}", self.a, self.b)
+///     }
+/// }
+///
+/// let a = A { a: 123, b: false };
+/// assert_eq!(r#""123<>false""#, serde_json::to_string(&a).unwrap());
+/// ```
+///
+/// [`Display`]: std::fmt::Display
+/// [`FromStr`]: std::str::FromStr
+#[proc_macro_derive(SerializeDisplay)]
+pub fn derive_serialize_display(item: TokenStream) -> TokenStream {
+    // Convert any error message into a nice compiler error
+    let res = match serialize_display(item) {
+        Ok(res) => res,
+        Err(err) => err.to_compile_error(),
+    };
+    TokenStream::from(res)
+}
+
+fn serialize_display(item: TokenStream) -> Result<proc_macro2::TokenStream, Error> {
+    let input = syn::parse::<DeriveInput>(item)?;
+    let ident = input.ident;
+    Ok(quote! {
+        #[automatically_derived]
+        impl<'de> serde::Serialize for #ident
+        where
+            Self: std::fmt::Display,
+        {
+            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+            where
+                S: serde::Serializer,
+            {
+                serializer.serialize_str(&self.to_string())
+            }
+        }
+    })
+}
diff --git a/src/lib.rs b/src/lib.rs
index 2480fed5..2c38a55a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -380,6 +380,9 @@ pub struct Same;
 /// Another use case is types with [`Display`] and [`FromStr`] implementations, but without serde
 /// support, which can be found in some crates.
 ///
+/// If you control the type you want to de/serialize, you can instead use the two derive macros, [`SerializeDisplay`] and [`DeserializeFromStr`].
+/// They properly implement the traits [`Serialize`] and [`Deserialize`] such that user of the type no longer have to use the `serde_as` system.
+///
 /// The same functionality is also available as [`serde_with::rust::display_fromstr`][crate::rust::display_fromstr] compatible with serde's with-annotation.
 ///
 /// # Examples
diff --git a/src/rust.rs b/src/rust.rs
index 557db43e..50fb97b3 100644
--- a/src/rust.rs
+++ b/src/rust.rs
@@ -21,6 +21,9 @@ use std::{
 /// It can be very useful for serialization formats like JSON, which do not support integer
 /// numbers and have to resort to strings to represent them.
 ///
+/// If you control the type you want to de/serialize, you can instead use the two derive macros, [`SerializeDisplay`] and [`DeserializeFromStr`].
+/// They properly implement the traits [`Serialize`] and [`Deserialize`] such that user of the type no longer have to use the with-attribute.
+///
 /// The same functionality is also available as [`serde_with::DisplayFromStr`][crate::DisplayFromStr] compatible with serde's with-annotation.
 ///
 /// # Examples


Test patch:
diff --git a/serde_with_macros/tests/deserialize_fromstr.rs b/serde_with_macros/tests/deserialize_fromstr.rs
new file mode 100644
index 00000000..cbdfc767
--- /dev/null
+++ b/serde_with_macros/tests/deserialize_fromstr.rs
@@ -0,0 +1,57 @@
+use pretty_assertions::assert_eq;
+use serde_with_macros::DeserializeFromStr;
+use std::{
+    num::ParseIntError,
+    str::{FromStr, ParseBoolError},
+};
+
+#[derive(Debug, PartialEq, DeserializeFromStr)]
+struct A {
+    a: u32,
+    b: bool,
+}
+
+impl FromStr for A {
+    type Err = String;
+
+    /// Parse a value like `123<>true`
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let mut parts = s.split("<>");
+        let number = parts
+            .next()
+            .ok_or_else(|| "Missing first value".to_string())?
+            .parse()
+            .map_err(|err: ParseIntError| err.to_string())?;
+        let bool = parts
+            .next()
+            .ok_or_else(|| "Missing second value".to_string())?
+            .parse()
+            .map_err(|err: ParseBoolError| err.to_string())?;
+        Ok(Self { a: number, b: bool })
+    }
+}
+
+#[test]
+fn test_deserialize_fromstr() {
+    let a: A = serde_json::from_str("\"159<>true\"").unwrap();
+    assert_eq!(A { a: 159, b: true }, a);
+    let a: A = serde_json::from_str("\"999<>false\"").unwrap();
+    assert_eq!(A { a: 999, b: false }, a);
+    let a: A = serde_json::from_str("\"0<>true\"").unwrap();
+    assert_eq!(A { a: 0, b: true }, a);
+}
+
+#[test]
+fn test_deserialize_fromstr_in_vec() {
+    let json = r#"[
+  "123<>false",
+  "0<>true",
+  "999<>true"
+]"#;
+    let expected = vec![
+        A { a: 123, b: false },
+        A { a: 0, b: true },
+        A { a: 999, b: true },
+    ];
+    assert_eq!(expected, serde_json::from_str::<Vec<A>>(&json).unwrap());
+}
diff --git a/serde_with_macros/tests/serialize_display.rs b/serde_with_macros/tests/serialize_display.rs
new file mode 100644
index 00000000..c8c47af9
--- /dev/null
+++ b/serde_with_macros/tests/serialize_display.rs
@@ -0,0 +1,40 @@
+use pretty_assertions::assert_eq;
+use serde_with_macros::SerializeDisplay;
+use std::fmt;
+
+#[derive(SerializeDisplay)]
+struct A {
+    a: u32,
+    b: bool,
+}
+
+impl fmt::Display for A {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "->{} <> {}<-", self.a, self.b)
+    }
+}
+
+#[test]
+fn test_serialize_display() {
+    let a = A { a: 123, b: false };
+    assert_eq!(r#""->123 <> false<-""#, serde_json::to_string(&a).unwrap());
+    let a = A { a: 0, b: true };
+    assert_eq!(r#""->0 <> true<-""#, serde_json::to_string(&a).unwrap());
+    let a = A { a: 999, b: true };
+    assert_eq!(r#""->999 <> true<-""#, serde_json::to_string(&a).unwrap());
+}
+
+#[test]
+fn test_serialize_display_in_vec() {
+    let v = vec![
+        A { a: 123, b: false },
+        A { a: 0, b: true },
+        A { a: 999, b: true },
+    ];
+    let expected = r#"[
+  "->123 <> false<-",
+  "->0 <> true<-",
+  "->999 <> true<-"
+]"#;
+    assert_eq!(expected, serde_json::to_string_pretty(&v).unwrap());
+}