I have a pet pattern for dealing with cases where serde's annotations don't suffice. Basically, I make separate types for serialization and for use around the actual code.
// used around the code
#[derive(Debug)]
struct Container {
creation: DateTime<Utc>,
items: BTreeMap<DateTime<Utc>, Item>
}
// used strictly for serialization
#[serde(deny_unknown_fields)]
#[derive(Deserialize, Debug)]
struct Cereal {
creation: FromStrWrapper<DateTime<Utc>>,
items: BTreeMap<FromStrWrapper<DateTime<Utc>>, Item>,
}
This gives you the chance to add additional validation (e.g. to check type invariants that involve multiple fields), and, in cases such as yours, it lets the deserialized type be slightly different from the real type; notice how I wrapped the DateTimes in a struct called FromStrWrapper
, which can be defined like this:
// Implements Deserialize using FromStr
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
struct FromStrWrapper<T>(T);
impl<'de, T> Deserialize<'de> for FromStrWrapper<T>
where T: FromStr,
T::Err: Display,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
T::from_str(&s).map_err(serde::de::Error::custom).map(FromStrWrapper)
}
}
And then you just need a method to convert from Cereal
to the true type (I call it validate
as part of my pet pattern because I often have more error-checking code in it, but feel free to call it whatever).
impl Cereal {
fn validate(self) -> Container {
Container {
creation: self.creation.0,
items: self.items.into_iter().map(|(k, v)| (k.0, v)).collect(),
}
}
}
(alternatively, Cereal can have Strings instead of FromStrWrapper<DateTime<Utc>>
, and validate
could use FromStr
on the strings and return a Result
; but that will produce error messages with suboptimal line/column information)
Cereal
and its fields can be made pub
, letting the caller deserialize one and call validate
; or you can choose to take care of this yourself by implementing Deserialize
for Container
:
impl<'de> Deserialize<'de> for Container {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>,
{
Cereal::deserialize(deserializer).map(Cereal::validate)
}
}
Full playground (edit: link fixed)