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.