Skip to main content

toasty_sql/
value_json.rs

1//! JSON encoding for `stmt::Value`s stored in document-backed columns
2//! (MySQL `JSON`, SQLite TEXT via the JSON1 extension, and eventually
3//! PostgreSQL `jsonb` for `#[document]`-marked fields). The conversion is
4//! intentionally a plain pair of functions rather than a `Serialize` /
5//! `Deserialize` impl on `stmt::Value`: the encoding is opinionated
6//! (UUIDs / decimals / timestamps as JSON strings) and matches the
7//! per-column TEXT encoding the same scalar would have at the SQL level.
8//! Backends with typed document storage (BSON, DynamoDB) need different
9//! representations.
10//!
11//! Decoding requires the element type from the schema — `Value::Uuid` vs
12//! `Value::String` are both JSON strings on the wire, and only the
13//! caller's `stmt::Type` distinguishes them.
14
15use serde_json::Value as Json;
16use toasty_core::stmt::{self, Value};
17
18/// Encode a scalar `stmt::Value` as a `serde_json::Value`. Panics on
19/// shapes that have no JSON representation (`Record`, nested `List`,
20/// `Bytes`, `SparseRecord`, `Null` records, NaN / infinity).
21pub fn value_to_json(value: &Value) -> Json {
22    match value {
23        Value::Null => Json::Null,
24        Value::Bool(v) => Json::Bool(*v),
25        Value::I8(v) => Json::Number((*v).into()),
26        Value::I16(v) => Json::Number((*v).into()),
27        Value::I32(v) => Json::Number((*v).into()),
28        Value::I64(v) => Json::Number((*v).into()),
29        Value::U8(v) => Json::Number((*v).into()),
30        Value::U16(v) => Json::Number((*v).into()),
31        Value::U32(v) => Json::Number((*v).into()),
32        Value::U64(v) => Json::Number((*v).into()),
33        Value::F32(v) => serde_json::Number::from_f64((*v).into())
34            .map(Json::Number)
35            .unwrap_or(Json::Null),
36        Value::F64(v) => serde_json::Number::from_f64(*v)
37            .map(Json::Number)
38            .unwrap_or(Json::Null),
39        Value::String(v) => Json::String(v.clone()),
40        Value::Uuid(v) => Json::String(v.to_string()),
41        #[cfg(feature = "rust_decimal")]
42        Value::Decimal(v) => Json::String(v.to_string()),
43        #[cfg(feature = "bigdecimal")]
44        Value::BigDecimal(v) => Json::String(v.to_string()),
45        #[cfg(feature = "jiff")]
46        Value::Timestamp(v) => Json::String(v.to_string()),
47        #[cfg(feature = "jiff")]
48        Value::Zoned(v) => Json::String(v.to_string()),
49        #[cfg(feature = "jiff")]
50        Value::Date(v) => Json::String(v.to_string()),
51        #[cfg(feature = "jiff")]
52        Value::Time(v) => Json::String(v.to_string()),
53        #[cfg(feature = "jiff")]
54        Value::DateTime(v) => Json::String(v.to_string()),
55        _ => todo!("encode {value:?} as JSON"),
56    }
57}
58
59/// Decode a `serde_json::Value` into a `stmt::Value` of type `ty`.
60pub fn value_from_json(json: Json, ty: &stmt::Type) -> Value {
61    match (ty, json) {
62        (_, Json::Null) => Value::Null,
63        (stmt::Type::Bool, Json::Bool(v)) => Value::Bool(v),
64        (stmt::Type::String, Json::String(v)) => Value::String(v),
65        (stmt::Type::Uuid, Json::String(v)) => {
66            Value::Uuid(v.parse().expect("invalid UUID in JSON"))
67        }
68        (stmt::Type::I8, Json::Number(n)) => Value::I8(n.as_i64().unwrap() as i8),
69        (stmt::Type::I16, Json::Number(n)) => Value::I16(n.as_i64().unwrap() as i16),
70        (stmt::Type::I32, Json::Number(n)) => Value::I32(n.as_i64().unwrap() as i32),
71        (stmt::Type::I64, Json::Number(n)) => Value::I64(n.as_i64().unwrap()),
72        (stmt::Type::U8, Json::Number(n)) => Value::U8(n.as_u64().unwrap() as u8),
73        (stmt::Type::U16, Json::Number(n)) => Value::U16(n.as_u64().unwrap() as u16),
74        (stmt::Type::U32, Json::Number(n)) => Value::U32(n.as_u64().unwrap() as u32),
75        (stmt::Type::U64, Json::Number(n)) => Value::U64(n.as_u64().unwrap()),
76        (stmt::Type::F32, Json::Number(n)) => Value::F32(n.as_f64().unwrap() as f32),
77        (stmt::Type::F64, Json::Number(n)) => Value::F64(n.as_f64().unwrap()),
78        #[cfg(feature = "rust_decimal")]
79        (stmt::Type::Decimal, Json::String(v)) => {
80            Value::Decimal(v.parse().expect("invalid Decimal in JSON"))
81        }
82        #[cfg(feature = "bigdecimal")]
83        (stmt::Type::BigDecimal, Json::String(v)) => {
84            Value::BigDecimal(v.parse().expect("invalid BigDecimal in JSON"))
85        }
86        #[cfg(feature = "jiff")]
87        (stmt::Type::Timestamp, Json::String(v)) => {
88            Value::Timestamp(v.parse().expect("invalid Timestamp in JSON"))
89        }
90        #[cfg(feature = "jiff")]
91        (stmt::Type::Zoned, Json::String(v)) => {
92            Value::Zoned(v.parse().expect("invalid Zoned in JSON"))
93        }
94        #[cfg(feature = "jiff")]
95        (stmt::Type::Date, Json::String(v)) => {
96            Value::Date(v.parse().expect("invalid Date in JSON"))
97        }
98        #[cfg(feature = "jiff")]
99        (stmt::Type::Time, Json::String(v)) => {
100            Value::Time(v.parse().expect("invalid Time in JSON"))
101        }
102        #[cfg(feature = "jiff")]
103        (stmt::Type::DateTime, Json::String(v)) => {
104            Value::DateTime(v.parse().expect("invalid DateTime in JSON"))
105        }
106        (ty, json) => todo!("decode JSON value {json:?} as {ty:?}"),
107    }
108}
109
110/// Encode a `Value::List` as a JSON array document. The element type
111/// drives per-element encoding via [`value_to_json`].
112pub fn value_list_to_json(value: &Value) -> Json {
113    let Value::List(items) = value else {
114        unreachable!("value_list_to_json called on {value:?}")
115    };
116    Json::Array(items.iter().map(value_to_json).collect())
117}
118
119/// Decode a JSON array document into a `Value::List`, using `elem_ty`
120/// as the per-element type.
121pub fn value_list_from_json(json: Json, elem_ty: &stmt::Type) -> Value {
122    let Json::Array(items) = json else {
123        panic!("expected JSON array for Vec<scalar> column, got {json:?}")
124    };
125    Value::List(
126        items
127            .into_iter()
128            .map(|v| value_from_json(v, elem_ty))
129            .collect(),
130    )
131}