rustserderust-chrono

Deserializing a DateTime from a string millisecond timestamp with serde


I receive a millisecond timestamp from an external API as a JSON string attribute.

{"time":"1526522699918"}

Whats the best way to use Serde to parse the millisecond timestamp as a string?

The ts_milliseconds option works with a millisecond timestamp as an integer, but throws an error when using a string.

Example - Rust Playground

#[macro_use]
extern crate serde_derive;
extern crate chrono;
use chrono::serde::ts_milliseconds;
use chrono::{DateTime, Utc};

#[derive(Deserialize, Serialize)]
struct S {
    #[serde(with = "ts_milliseconds")]
    time: DateTime<Utc>,
}

fn main() {
    serde_json::from_str::<S>(r#"{"time":1526522699918}"#).unwrap(); // millisecond timestamp as a integer
    serde_json::from_str::<S>(r#"{"time":"1526522699918"}"#).unwrap(); // millisecond timestamp as an string
}

Error message:

Error("invalid type: string \"1526522699918\", expected a unix timestamp in milliseconds", line: 1, column: 23)'

Solution

  • Abstracting over the serialized for of DateTime is possible using the TimestampMilliSeconds type from serde_with. With it, you can serialize to / deserialize from floats, integers, or strings. You need to enable the chrono feature for serde_with.

    The first argument (here String) configures the serialization behavior. In case of String this means the DateTime will be serialized as a string containing a Unix timestamp in milliseconds.

    The second argument (here Flexible) allows configuring the deserialization behavior. Flexible means that it will deserialize from floats, integers, and strings without returning an error. You can use that to get the main function from the question to run. The other option is Strict, which only deserializes the format from the first argument. For this example this means it would only deserialize the time as a string, but would return an error when encountering the integer.

    use ::chrono::{DateTime, Utc};
    use serde_with::TimestampMilliSeconds;
    use serde_with::formats::Flexible;
    
    #[serde_with::serde_as]
    #[derive(serde::Deserialize, serde::Serialize)]
    struct S {
        #[serde_as(as = "TimestampMilliSeconds<String, Flexible>")]
        time: DateTime<Utc>,
    }
    
    fn main() {
        serde_json::from_str::<S>(r#"{"time":1526522699918}"#).unwrap(); // millisecond timestamp as a integer
        serde_json::from_str::<S>(r#"{"time":"1526522699918"}"#).unwrap(); // millisecond timestamp as an string
    }