Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make serialize/deserialize composable #2650

Closed
mokeyish opened this issue Nov 18, 2023 · 1 comment
Closed

Make serialize/deserialize composable #2650

mokeyish opened this issue Nov 18, 2023 · 1 comment

Comments

@mokeyish
Copy link

mokeyish commented Nov 18, 2023

Because Rust has the orphan rule, if you use the trait approach, you may encounter issues related to the orphan rule. It becomes challenging to provide serialization functionality for third-party structures.

Recently, referring to the documentation Date in a custom format, I implemented serde_str, and it seems to work well. However, I'm facing difficulties when dealing with Option. Is there a way to combine serde_str and serde_opt? This is quite common.

The code for implementing serde_str for structs that implement the FromStr and ToString traits is as follows:

pub mod serde_str {
    use std::str::FromStr;
    use serde::{self, Deserialize, Serializer, Deserializer};

    pub fn serialize<S>(
        data: &dyn ToString,
        serializer: S,
    ) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = data.to_string();
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D, E: std::fmt::Debug, T: FromStr<Err = E>>(
        deserializer: D,
    ) -> Result<T, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        T::from_str(&s).map_err(|err| serde::de::Error::custom(format!("{:?}", err)))
    }
}

Then use as:

fn main() {
    #[derive(Debug, Serialize, Deserialize)]
    struct A {
        #[serde(with = "serde_str")]
        b: B,
    }

    #[derive(Debug)]
    pub struct B(usize);


    impl FromStr for B {
        type Err = ();

        fn from_str(s: &str) -> Result<Self, Self::Err> {
            usize::from_str(s).map(B).map_err(|_|())
        }
    }

    impl ToString for B {
        fn to_string(&self) -> String {
            format!("{}", self.0)
        }
    }


    let a = r#"
    { "b": "100" }
    "#;

    let a: A = serde_json::from_str(a).unwrap();

    assert_eq!(a.b.0, 100);

    assert_eq!(serde_json::to_string(&a).unwrap(), "{\"b\":\"100\"}");
}

If I change b: B to b: Option<B>, it seems to introduce new challenges. I'm looking for something like #[serde(with = "serde_opt(serde_str)")], but it appears that such a solution doesn't exist.

This combination of ideas is somewhat reminiscent of how rust-bakery/nom handles things.

@dtolnay
Copy link
Member

dtolnay commented May 15, 2024

#723 has a lot of prior discussion on this topic.

@dtolnay dtolnay closed this as completed May 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants