How to use a custom serde deserializer for chrono timestamps?
While @edwardw's answer is technically correct it IMHO contains too much boilerplate.
NaiveDataTime
implements FromStr
which means you can write a reusable generic deserializer function.
A convoluted example - did add the age
field (u8
) represented as string in the JSON. Just to demonstrate that you can use it for anything that implements FromStr
.
use std::fmt::Display;
use std::str::FromStr;
use chrono::NaiveDateTime;
use serde::{de, Deserialize, Deserializer};
#[derive(Deserialize, Debug)]
struct MyJson {
name: String,
#[serde(deserialize_with = "deserialize_from_str")]
timestamp: NaiveDateTime,
#[serde(deserialize_with = "deserialize_from_str")]
age: u8,
}
// You can use this deserializer for any type that implements FromStr
// and the FromStr::Err implements Display
fn deserialize_from_str<'de, S, D>(deserializer: D) -> Result<S, D::Error>
where
S: FromStr, // Required for S::from_str...
S::Err: Display, // Required for .map_err(de::Error::custom)
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
S::from_str(&s).map_err(de::Error::custom)
}
fn main() {
let result: MyJson = serde_json::from_str(
r#"{"name": "asdf", "timestamp": "2019-08-15T17:41:18.106108", "age": "11"}"#,
)
.unwrap();
println!("{:?}", result);
}
It's even easier if you want to specify format (use NaiveDateTime::parse_from_str
):
use chrono::NaiveDateTime;
use serde::{de, Deserialize, Deserializer};
#[derive(Deserialize, Debug)]
struct MyJson {
name: String,
#[serde(deserialize_with = "naive_date_time_from_str")]
timestamp: NaiveDateTime,
}
fn naive_date_time_from_str<'de, D>(deserializer: D) -> Result<NaiveDateTime, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S.%f").map_err(de::Error::custom)
}
fn main() {
let result: MyJson =
serde_json::from_str(r#"{"name": "asdf", "timestamp": "2019-08-15T17:41:18.106108"}"#)
.unwrap();
println!("{:?}", result);
}
#[serde(deserialize_with = "path")]
documentation:
Deserialize this field using a function that is different from its implementation of
Deserialize
. The given function must be callable asfn<'de, D>(D) -> Result<T, D::Error> where D: Deserializer<'de>
, although it may also be generic overT
. Fields used withdeserialize_with
are not required to implementDeserialize
.
This is rather involved, but the following works:
use chrono::NaiveDateTime;
use serde::de;
use serde::Deserialize;
use std::fmt;
struct NaiveDateTimeVisitor;
impl<'de> de::Visitor<'de> for NaiveDateTimeVisitor {
type Value = NaiveDateTime;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a string represents chrono::NaiveDateTime")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S.%f") {
Ok(t) => Ok(t),
Err(_) => Err(de::Error::invalid_value(de::Unexpected::Str(s), &self)),
}
}
}
fn from_timestamp<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
where
D: de::Deserializer<'de>,
{
d.deserialize_str(NaiveDateTimeVisitor)
}
#[derive(Deserialize, Debug)]
struct MyJson {
name: String,
#[serde(deserialize_with = "from_timestamp")]
timestamp: NaiveDateTime,
}
fn main() {
let result: MyJson =
serde_json::from_str(r#"{"name": "asdf", "timestamp": "2019-08-15T17:41:18.106108"}"#)
.unwrap();
println!("{:?}", result);
}