postrust_core/
row_json.rs1pub fn row_to_json(row: &sqlx::postgres::PgRow) -> serde_json::Value {
15 use sqlx::{Column, Row, TypeInfo};
16
17 let mut map = serde_json::Map::new();
18
19 for column in row.columns() {
24 let name = column.name();
25 let value = decode(row, name, column.type_info().name());
26 map.insert(name.to_string(), value);
27 }
28
29 serde_json::Value::Object(map)
30}
31
32pub fn row_to_values(row: &sqlx::postgres::PgRow) -> Vec<serde_json::Value> {
34 use sqlx::{Column, Row, TypeInfo};
35
36 row.columns()
37 .iter()
38 .map(|column| decode(row, column.ordinal(), column.type_info().name()))
39 .collect()
40}
41
42fn decode<I>(row: &sqlx::postgres::PgRow, index: I, type_name: &str) -> serde_json::Value
44where
45 I: sqlx::ColumnIndex<sqlx::postgres::PgRow> + Copy,
46{
47 use sqlx::Row;
48 let name = index;
50
51 let value = match type_name {
52 "INT2" | "SMALLINT" => row
53 .try_get::<i16, _>(name)
54 .ok()
55 .map(|v| serde_json::Value::Number(v.into())),
56 "INT4" | "INT" | "INTEGER" => row
57 .try_get::<i32, _>(name)
58 .ok()
59 .map(|v| serde_json::Value::Number(v.into())),
60 "INT8" | "BIGINT" => row
61 .try_get::<i64, _>(name)
62 .ok()
63 .map(|v| serde_json::Value::Number(v.into())),
64 "FLOAT4" | "REAL" => row
65 .try_get::<f32, _>(name)
66 .ok()
67 .and_then(|v| serde_json::Number::from_f64(v as f64))
68 .map(serde_json::Value::Number),
69 "FLOAT8" | "DOUBLE PRECISION" => row
70 .try_get::<f64, _>(name)
71 .ok()
72 .and_then(serde_json::Number::from_f64)
73 .map(serde_json::Value::Number),
74 "NUMERIC" | "DECIMAL" => row
83 .try_get::<sqlx::types::BigDecimal, _>(name)
84 .ok()
85 .map(|v| {
86 let text = v.to_string();
87 serde_json::from_str::<serde_json::Value>(&text)
88 .ok()
89 .filter(serde_json::Value::is_number)
90 .unwrap_or(serde_json::Value::String(text))
91 }),
92 "BOOL" | "BOOLEAN" => row
93 .try_get::<bool, _>(name)
94 .ok()
95 .map(serde_json::Value::Bool),
96 "JSON" | "JSONB" => row.try_get::<serde_json::Value, _>(name).ok(),
97 "UUID" => row
98 .try_get::<sqlx::types::Uuid, _>(name)
99 .ok()
100 .map(|v| serde_json::Value::String(v.to_string())),
101 "TIMESTAMPTZ" | "TIMESTAMP WITH TIME ZONE" => row
102 .try_get::<chrono::DateTime<chrono::Utc>, _>(name)
103 .ok()
104 .map(|v| serde_json::Value::String(v.to_rfc3339())),
105 "TIMESTAMP" | "TIMESTAMP WITHOUT TIME ZONE" => row
110 .try_get::<chrono::NaiveDateTime, _>(name)
111 .ok()
112 .map(|v| serde_json::Value::String(v.format("%Y-%m-%dT%H:%M:%S%.f").to_string())),
113 "DATE" => row
114 .try_get::<chrono::NaiveDate, _>(name)
115 .ok()
116 .map(|v| serde_json::Value::String(v.to_string())),
117 "TIME" | "TIME WITHOUT TIME ZONE" => row
118 .try_get::<chrono::NaiveTime, _>(name)
119 .ok()
120 .map(|v| serde_json::Value::String(v.to_string())),
121 _ => row
122 .try_get::<String, _>(name)
123 .ok()
124 .map(serde_json::Value::String),
125 };
126
127 value.unwrap_or(serde_json::Value::Null)
128}
129
130#[cfg(test)]
131mod tests {
132 #[test]
135 fn a_timestamp_without_a_zone_is_written_in_iso_8601() {
136 let format = "%Y-%m-%dT%H:%M:%S%.f";
137
138 let with_fraction = chrono::NaiveDateTime::parse_from_str(
139 "2015-12-08 04:22:57.472738",
140 "%Y-%m-%d %H:%M:%S%.f",
141 )
142 .unwrap();
143 assert_eq!(
144 with_fraction.format(format).to_string(),
145 "2015-12-08T04:22:57.472738"
146 );
147
148 let whole_second =
149 chrono::NaiveDateTime::parse_from_str("2018-01-02 00:00:00", "%Y-%m-%d %H:%M:%S")
150 .unwrap();
151 assert_eq!(
152 whole_second.format(format).to_string(),
153 "2018-01-02T00:00:00"
154 );
155 }
156}