Serde: Support for std::time::Instant

Created on 6 Sep 2018  路  3Comments  路  Source: serde-rs/serde

I ran into a case where I was looking to serialize a struct that contained a field of type std::time::Instant.

This is currently not supported by serde so I thought I would take the opportunity and try to add an implementation for it. I looked at https://github.com/serde-rs/serde/pull/476 and https://github.com/serde-rs/serde/pull/949 as starting points.

I think the main issue with an Instant implementation stems from the following:

Instants are opaque types that can only be compared to one another. There is no method to get "the number of seconds" from an instant. Instead, it only allows measuring the duration between two instants (or comparing two instants).

Duration is composed of a whole number of seconds and a fractional part represented in nanoseconds. SystemTime is able to anchor off of UNIX_EPOCH. Therefore, there are ways to obtain the underlying data representing these structs.

Since there is nothing similar for Instant, and it can only be compared with other Instants, is an implementation for this currently a dead end? I would love to be able to tackle this, but I am not sure if serialization is possible since there is no way to obtain the underlying data representation.

support

Most helpful comment

As a hacky approximate workaround you could serialize an approximation of an Instant in the following way. Note that this provides no upper bound on how wrong the value might be after serializing and deserializing. It might be within a few microseconds of the original value most of the time but wrong by several hours other times, especially if the system time skips around.

(The real fix is still to use a different type.)

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use std::time::Instant;

#[derive(Serialize, Deserialize, Debug)]
struct S {
    #[serde(with = "approx_instant")]
    time: Instant,
}

mod approx_instant {
    use std::time::{Instant, SystemTime};
    use serde::{Serialize, Serializer, Deserialize, Deserializer, de::Error};

    pub fn serialize<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let system_now = SystemTime::now();
        let instant_now = Instant::now();
        let approx = system_now - (instant_now - *instant);
        approx.serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
    where
        D: Deserializer<'de>,
    {
        let de = SystemTime::deserialize(deserializer)?;
        let system_now = SystemTime::now();
        let instant_now = Instant::now();
        let duration = system_now.duration_since(de).map_err(Error::custom)?;
        let approx = instant_now - duration;
        Ok(approx)
    }
}

fn main() {
    let s = S { time: Instant::now() };
    println!("before: {:?}\n", s);

    let j = serde_json::to_string(&s).unwrap();
    println!("json: {}\n", j);

    let s: S = serde_json::from_str(&j).unwrap();
    println!("after: {:?}\n", s);
}

All 3 comments

If there is no way to obtain some form of serializable underlying data from a type, then there is no way it can be serialized. Or in the reverse situation if there is no way to instantiate a type from some deserializable underlying data, it isn't possible to deserialize the type.

In this case it looks like Instant is carefully designed to expose only what is necessary for the use case described in the docs, a monotonically nondecreasing clock that can be compared relative to other instants. I am closing this issue because the API is not sufficient to enable Instant to be serialized or deserialized.

As a hacky approximate workaround you could serialize an approximation of an Instant in the following way. Note that this provides no upper bound on how wrong the value might be after serializing and deserializing. It might be within a few microseconds of the original value most of the time but wrong by several hours other times, especially if the system time skips around.

(The real fix is still to use a different type.)

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use std::time::Instant;

#[derive(Serialize, Deserialize, Debug)]
struct S {
    #[serde(with = "approx_instant")]
    time: Instant,
}

mod approx_instant {
    use std::time::{Instant, SystemTime};
    use serde::{Serialize, Serializer, Deserialize, Deserializer, de::Error};

    pub fn serialize<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let system_now = SystemTime::now();
        let instant_now = Instant::now();
        let approx = system_now - (instant_now - *instant);
        approx.serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
    where
        D: Deserializer<'de>,
    {
        let de = SystemTime::deserialize(deserializer)?;
        let system_now = SystemTime::now();
        let instant_now = Instant::now();
        let duration = system_now.duration_since(de).map_err(Error::custom)?;
        let approx = instant_now - duration;
        Ok(approx)
    }
}

fn main() {
    let s = S { time: Instant::now() };
    println!("before: {:?}\n", s);

    let j = serde_json::to_string(&s).unwrap();
    println!("json: {}\n", j);

    let s: S = serde_json::from_str(&j).unwrap();
    println!("after: {:?}\n", s);
}

If you are only interested by elapsed time, you can serialize and deserialize like this :

use std::time::{Duration, Instant};

use serde::{Serialize, Serializer, Deserialize, Deserializer};
use serde::de::Error;

pub fn serialize<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let duration = instant.elapsed();
    duration.serialize(serializer)
}

pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
where
    D: Deserializer<'de>,
{
    let duration = Duration::deserialize(deserializer)?;
    let now = Instant::now();
    let instant = now.checked_sub(duration).ok_or_else(|| Error::custom("Erreur checked_add"))?;
    Ok(instant)
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dtolnay picture dtolnay  路  3Comments

dtolnay picture dtolnay  路  4Comments

mwu-tow picture mwu-tow  路  3Comments

tangkhaiphuong picture tangkhaiphuong  路  3Comments

pwoolcoc picture pwoolcoc  路  3Comments