pub fn row_to_json(row: &sqlx::postgres::PgRow) -> serde_json::Value {
use sqlx::{Column, Row, TypeInfo};
let mut map = serde_json::Map::new();
for column in row.columns() {
let name = column.name();
let type_name = column.type_info().name();
let value = match type_name {
"INT2" | "SMALLINT" => row
.try_get::<i16, _>(name)
.ok()
.map(|v| serde_json::Value::Number(v.into())),
"INT4" | "INT" | "INTEGER" => row
.try_get::<i32, _>(name)
.ok()
.map(|v| serde_json::Value::Number(v.into())),
"INT8" | "BIGINT" => row
.try_get::<i64, _>(name)
.ok()
.map(|v| serde_json::Value::Number(v.into())),
"FLOAT4" | "REAL" => row
.try_get::<f32, _>(name)
.ok()
.and_then(|v| serde_json::Number::from_f64(v as f64))
.map(serde_json::Value::Number),
"FLOAT8" | "DOUBLE PRECISION" => row
.try_get::<f64, _>(name)
.ok()
.and_then(serde_json::Number::from_f64)
.map(serde_json::Value::Number),
"NUMERIC" | "DECIMAL" => {
row.try_get::<sqlx::types::BigDecimal, _>(name)
.ok()
.map(|v| {
let text = v.to_string();
serde_json::from_str::<serde_json::Value>(&text)
.ok()
.filter(serde_json::Value::is_number)
.unwrap_or(serde_json::Value::String(text))
})
}
"BOOL" | "BOOLEAN" => row
.try_get::<bool, _>(name)
.ok()
.map(serde_json::Value::Bool),
"JSON" | "JSONB" => row.try_get::<serde_json::Value, _>(name).ok(),
"UUID" => row
.try_get::<sqlx::types::Uuid, _>(name)
.ok()
.map(|v| serde_json::Value::String(v.to_string())),
"TIMESTAMPTZ" | "TIMESTAMP WITH TIME ZONE" => row
.try_get::<chrono::DateTime<chrono::Utc>, _>(name)
.ok()
.map(|v| serde_json::Value::String(v.to_rfc3339())),
"TIMESTAMP" | "TIMESTAMP WITHOUT TIME ZONE" => row
.try_get::<chrono::NaiveDateTime, _>(name)
.ok()
.map(|v| serde_json::Value::String(v.format("%Y-%m-%dT%H:%M:%S%.f").to_string())),
"DATE" => row
.try_get::<chrono::NaiveDate, _>(name)
.ok()
.map(|v| serde_json::Value::String(v.to_string())),
"TIME" | "TIME WITHOUT TIME ZONE" => row
.try_get::<chrono::NaiveTime, _>(name)
.ok()
.map(|v| serde_json::Value::String(v.to_string())),
_ => row
.try_get::<String, _>(name)
.ok()
.map(serde_json::Value::String),
};
map.insert(name.to_string(), value.unwrap_or(serde_json::Value::Null));
}
serde_json::Value::Object(map)
}
#[cfg(test)]
mod tests {
#[test]
fn a_timestamp_without_a_zone_is_written_in_iso_8601() {
let format = "%Y-%m-%dT%H:%M:%S%.f";
let with_fraction = chrono::NaiveDateTime::parse_from_str(
"2015-12-08 04:22:57.472738",
"%Y-%m-%d %H:%M:%S%.f",
)
.unwrap();
assert_eq!(
with_fraction.format(format).to_string(),
"2015-12-08T04:22:57.472738"
);
let whole_second =
chrono::NaiveDateTime::parse_from_str("2018-01-02 00:00:00", "%Y-%m-%d %H:%M:%S")
.unwrap();
assert_eq!(
whole_second.format(format).to_string(),
"2018-01-02T00:00:00"
);
}
}