Howto Encapsulate Serializing/Deserializing into FromStr and Display

#[derive(Deserialize, Serialize, Debug)]
struct Data {
    label: String,
    value: i32,
    description: String,
}

Problem

Often when working with structural data and JSON you end up with a lot of boilerplate code like:

let my_data: Data  = serde_json::from_str(json_string).unwrap();
let response = serde_json__to_string(&my_responsedata).unwrap(); 

Don’t get me wrong. The interface from serde is as good as it gets, but deserializing is not our problem here, actually we’re making Data objects from String representations and vice versa.\

Why not just using rusts casting traits, so we can write that?

 let data: Data = json_string.parse().unwrap()
 let response = my_responsedata.to_string()

See? Way better! We’re now talking about types instead of how to transport the data between different formats and what our libraries are, we use.

The implementation is quite strait forward:

TryFrom

impl TryFrom<&str> for Data {
    type Error = Box<dyn Error>;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Ok(serde_json::from_str(value)?)
    }
}

let data = Data::try_from(input_data).unwrap()

FromStr

impl FromStr for Data {
    type Err = Box<dyn Error>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(serde_json::from_str(s)?)
    }
}

let data: Data = json_string.parse().unwrap();

Display

We could also directly implement the ToString trait, but that one is not recommended. If we implement Display we get the ToString for free.

impl Display for Data {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", serde_json::to_string(&self).unwrap_or_default())
    }
}

let response = my_responsedata.to_string()

Conclusion

Implementing these Traits is super easy, and it removes the boilerplate code significantly. It’s a no-brainer to implement them.