Deserializing with serde types acting as generic arguments

Hi,
I am trying to deserialize a json objbect into a BTreeMap with chrono::DateTime as keys.
I have no problem using from_str to deserialize a standalone DateTime, but I cannot find a compact way to deserialize the DateTime object when it is the key of a json map.
I basically want to deserialize Container objects but cannot find a way to decode Container.items, Container.creation works no problem.

Full example here:
gist and playground

Any ideas on how could I do that?

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)

1 Like