Deserialize json datetime to struct of dates

Hello.

I am looking to deserialize json dates to a struct of dates. I am comparing dates between events and using chrono with the NaiveDate for this. The data looks to be in the format of a DateTime. If I use the NaiveDate I get an error for trailing data when it parses it:

Error: Error("trailing input", line: 1, column: 675)

The code is:

use chrono::NaiveDate;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct Event {
    end: NaiveDate,
    start: NaiveDate,
}

pub fn process(events: Vec<Event>, start_date: NaiveDate, end_date: NaiveDate) -> Result<Vec<Event>, Box<dyn std::error::Error>> {
    let events = filter_range(events, start_date, end_date);
    Ok(events)
}

fn filter_range(events: Vec<Event>, start_date: NaiveDate, end_date: NaiveDate) -> Vec<Event> {
    events.into_iter().filter(|event: &Event| {
        (event.start >= start_date && event.start <= end_date) || (event.end >= start_date && event.end <= end_date)
    }).collect()
}

The data when parsed with a DateTime looks like this:

Event {
    end: 2024-11-03T16:00:00Z,
    start: 2024-11-01T16:00:00Z,
},

I would like the output to be:

Event {
    end: 2024-11-03,
    start: 2024-11-01,
},

How can I easily convert to a Date in the format of "yyyy-mm-dd" without needing to convert in the filter function? I need to pipe the data to a few functions for other fields so don't want to convert in the specific filter function. Ideally an annotation with a type from the json would be ideal but I have errors about data type mismatches if using NaiveDateTime and NaiveDate.

I have looked at annotations using serde_with and serde_as and also custom serializing and deserializing but haven't found anything that worked.

Any help is much appreciated.

NaiveDate implements From<NaiveDateTime> and NaiveDateTime does the same thing for NaiveDate, so I think you can use serde_as's FromInto for (de-)serialising NaiveDateTime directly into NaiveDate without needing to write your own (de-)serialisation logic:

#[derive(Serialize, Deserialize, Debug)]
pub struct Event {
    #[serde_as(as = "FromInto<NaiveDateTime>")]
    end: NaiveDate,
    #[serde_as(as = "FromInto<NaiveDateTime>")]
    start: NaiveDate,
}

If you only want to deserialise a NaiveDateTime into a NaiveDate, I would probably just do this:

use chrono::{NaiveDate, NaiveDateTime};
use serde::{de::Deserializer, Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct Event {
    #[serde(deserialize_with = "naive_date_time_to_naive_date")]
    end: NaiveDate,
    #[serde(deserialize_with = "naive_date_time_to_naive_date")]
    start: NaiveDate,
}

fn naive_date_time_to_naive_date<'de, D>(d: D) -> Result<NaiveDate, D::Error>
where
    D: Deserializer<'de>,
{
    Ok(NaiveDateTime::deserialize(d)?.into())
}

Playground.

For the first suggestion it has trailing input:

Error: Error("trailing input", line: 1, column: 675)

From looking up previously about this I think this is due to the "Z" being on the end of the date time value and this would need to be a DateTime<Utc> to work with this type. When I use this instead I get a different error:

The trait bound `NaiveDate: From<DateTime<Utc>>` is not satisfied
the trait `From<NaiveDateTime>` is implemented for `NaiveDate`
for that trait implementation, expected `NaiveDateTime`, found `DateTime<Utc>`
required for `DateTime<Utc>` to implement `Into<NaiveDate>`

I was thinking the annotations were simpler as I didn't need to implement anything myself. I have used the deserializing in your other example as this works:

fn naive_date_time_to_naive_date<'de, D>(d: D) -> Result<NaiveDate, D::Error>
where
    D: Deserializer<'de>,
{
    Ok(DateTime::<Utc>::deserialize(d)?.naive_utc().date())
}

What is the benefit of using a custom function for it, it provides more control and works around limitations the annotations offer? Would there be a way for the first suggestion or I should stick with the custom function?

The serde_as suggestion with [Try]FromInto only works with NaiveDateTime and not DateTime<Utc>, because the latter doesn't implement the required traits to make the conversion. I'd stick with the function you presented for deserialisation—which indeed offers you full control over the deserialisation of the annotated field, unlike serde_as which is macro that abstracts common deserialisation patterns not supported by serde directly; albeit not the pattern you need, i.e. parsing an ISO 8601 encoded UTC point in time into a NaiveDate—, although you can simplify .naive_utc().date() to just .date_naive().