serde icon indicating copy to clipboard operation
serde copied to clipboard

Document how to serialize using Display, deserialize using FromStr

Open dtolnay opened this issue 7 years ago • 15 comments

Often a type implements Display and FromStr but not Serialize and Deserialize.

The module from https://github.com/serde-rs/json/issues/329#issuecomment-305608405 should work.

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use std::fmt::{self, Display};
use std::str::FromStr;

#[derive(Debug)]
struct X;

impl Display for X {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("X")
    }
}

impl FromStr for X {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "X" => Ok(X),
            _ => Err("was not X"),
        }
    }
}

#[derive(Serialize, Deserialize, Debug)]
struct S {
    #[serde(with = "string")]
    x: X
}

mod string {
    use std::fmt::Display;
    use std::str::FromStr;

    use serde::{de, Serializer, Deserialize, Deserializer};

    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
    where
        T: Display,
        S: Serializer
    {
        serializer.collect_str(value)
    }

    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
    where
        T: FromStr,
        T::Err: Display,
        D: Deserializer<'de>
    {
        String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
    }
}

fn main() {
    let j = r#" { "x": "X" } "#;

    let s: S = serde_json::from_str(j).unwrap();
    println!("{:#?}", s);

    println!("{}", serde_json::to_string_pretty(&s).unwrap());
}

dtolnay avatar Jun 15 '18 20:06 dtolnay

I came here to suggest a simple way to do exactly this. I think it would be great if serde would provide a way to do that with some kind of derive-like thing.

However, in the case of deserialization, we should try avoid the unnecessary allocation of String. Since FromStr takes a &str, it should be possible to use a str directly using a visitor implementation.

stevenroose avatar Jan 15 '20 12:01 stevenroose

An alternative I cooked up from the original description: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2b642a9a0b8747fe59e9190cc273810c or with some fixed typos and added deref and derefmut in rs-ipfs/rust-ipfs/.../http/src/v0/support/serdesupport.rs which might be of interest to someone. Apologies if this wrapper is already in serde, but let me know please? :)

koivunej avatar Jul 09 '20 15:07 koivunej

Wouldn't it be possible to do something like this:


#[derive(Serialize, Deserialize)]
#[serde(stringify)]
pub MyType { ... }

impl fmt::Display for MyType { ... }

impl str::FromStr for MyType { ... }

stevenroose avatar Jul 26 '20 21:07 stevenroose

Wouldn't it be possible to do something like this:

It would be definitely useful, as far as I know, there is no concise way to use Display and FromStr for enum

synek317 avatar Sep 29 '21 21:09 synek317

#[serde(stringify)]

That's a really cool idea. Would be even better if this was separate for serialization and deserialization.

there is no concise way to use Display and FromStr for enum

Can you elaborate please?

MaxVerevkin avatar Aug 04 '22 12:08 MaxVerevkin

Can you elaborate please?

Well, there's not much to elaborate on. I only meant that we currently need a lot of code to de/serialize using FromStr and Display. IMHO that's a common need and it would be nice to have an attribute that would do the job for us.

synek317 avatar Aug 14 '22 11:08 synek317

Bump. I am in favour of creating such an attribute to automate this. Should we perhaps create a new issue? Or can this issue be edited to become an "improvement suggestion" of sorts?

cyqsimon avatar Oct 11 '22 10:10 cyqsimon

As noted in https://github.com/serde-rs/serde/pull/2017#issuecomment-903260861, the serde_with crate implements this as SerializeDisplay and DeserializeFromStr.

Using that crate, the example in the original issue:

use std::{fmt::{self, Display}, str::FromStr};

#[derive(Debug)]
struct X;

impl Display for X {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("X")
    }
}

impl FromStr for X {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "X" => Ok(X),
            _ => Err("was not X"),
        }
    }
}

can implement Serialize and Deserialize by simply adding:

use serde_with::{DeserializeFromStr, SerializeDisplay};

#[derive(DeserializeFromStr, SerializeDisplay, Debug)]
struct X;

silverlyra avatar Nov 20 '23 02:11 silverlyra

FWIW, I've been hitting this pretty regularly across many projects. And the nature of the problem is such that, in any given project, you probably only have a couple of types, so it doesn't really make sense to pull the entire serde_with. So, while this can be implemented by a third party, using that solution is annoying, and that, for me, points towards inclusion into the core serde.

matklad avatar Dec 24 '23 15:12 matklad