Reputation: 443
I would like to be able to serialize a struct containing PgRange<DateTime<Utc>>
however, #[derive(Serialize)]
fails for struct Reservation
with the error:
pub timespan: PgRange<DateTime<Utc>>,
^^^ the trait `Serialize` is not implemented for `PgRange<chrono::DateTime<chrono::Utc>>`
As a workaround, I am splitting the timespan field into two fields which works for serialization, but makes SQL more involved.
The data scheme is as follows:
CREATE TABLE reservation (
id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
timespan TSTZRANGE
);
INSERT INTO reservation(timespan) VALUES
(TSTZRANGE(now() + INTERVAL '0 hour', now() + INTERVAL '1 hour')),
(TSTZRANGE(now() + INTERVAL '2 hour', now() + INTERVAL '3 hour'));
and my working code example :
/*
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.5", features = ["runtime-tokio-native-tls" , "postgres", "chrono"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
*/
use chrono::prelude::*;
use serde::{Serialize, Deserialize};
use sqlx::FromRow;
use sqlx::postgres::types::PgRange;
use sqlx::postgres::PgPoolOptions;
#[derive(FromRow, Debug)]
pub struct Reservation {
pub id: i32,
pub timespan: PgRange<DateTime<Utc>>,
}
#[derive(FromRow, Serialize, Debug)]
pub struct ReservationWorkaroud {
pub id: i32,
pub start: DateTime<Utc>,
pub end: DateTime<Utc>,
}
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
let pool = PgPoolOptions::new()
.max_connections(2)
.connect(&"postgresql://postgres:postgres@localhost")
.await?;
// Ideally, I would like my struct to mirror db table as close as possible while being able to serialize/deserialize to/from Json
let select_query = sqlx::query_as::<_, Reservation>("SELECT id, timespan FROM reservation");
let reservations: Vec<Reservation> = select_query.fetch_all(&pool).await?;
dbg!("{:?}", reservations);
// With the workaroud struct, I am able to serialize it however, sql queries start to get complex especially for inserting:
let select_query = sqlx::query_as::<_, ReservationWorkaroud>("SELECT id, lower(timespan) AS start, upper(timespan) AS end FROM reservation");
let reservations: Vec<ReservationWorkaroud> = select_query.fetch_all(&pool).await?;
dbg!("{:?}", reservations);
Ok(())
}
How can I make struct Reservation
serializable ?
Based on @Caesar's answer, I went with the following approach:
#[derive(FromRow, Serialize,Debug)]
pub struct Reservation {
pub id: i32,
#[serde(serialize_with = "serialize_range", flatten)]
pub timespan: PgRange<DateTime<Utc>>,
}
fn serialize_range<S, T>(range: &PgRange<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: Serialize,
{
let PgRange { start, end } = range;
std::ops::Range { start, end }.serialize(serializer)
}
Upvotes: 0
Views: 1000
Reputation: 8544
There are quite a few ways to go on about this.
My favourite serde trick, use the container attribute #[serde(into = "ReservationWorkaround")]
on struct Reservation
and add an impl Into<ReservationWorkaround> for Reservation {}
. See here for an example of using the attribute.
Use a custom serialization function
#[derive(FromRow, Debug, Serialize)]
pub struct Reservation {
pub id: i32,
#[serde(serialize_with = "serialize_range", flatten)]
pub timespan: PgRange<DateTime<Utc>>,
}
fn serialize_range<S, T>(range: &PgRange<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: Serialize,
{
// You probably also want to convert start and end
// from Bound<DateTime<Utc>> to something else here.
// It is common to define a new struct like
#[derive(Serialize)] struct I64Range { start: i64, end: i64 }
// right here in this function
let PgRange { start, end } = range;
std::ops::Range { start, end }.serialize(serializer)
}
Bound<DateTime<Utc>>
. Maybe the serde_with
crate can help, but I'm not sure.#[derive(FromRow, Debug, Serialize)]
pub struct Reservation {
pub id: i32,
#[serde(with = "PgRangeRemote", flatten)]
pub timespan: PgRange<DateTime<Utc>>,
}
#[derive(Serialize)]
#[serde(remote = "PgRange")]
struct PgRangeRemote<T> {
start: std::ops::Bound<T>,
end: std::ops::Bound<T>,
}
Upvotes: 2