Skip to main content

rainier_orm/
json.rs

1//! [`Json<T>`] — a column whose Rust value is any `serde` type but whose
2//! storage is **JSON text on every dialect**.
3//!
4//! Databases disagree on JSON: MySQL/Postgres have a native `JSON` type, SQLite
5//! and Cloudflare D1 keep it in `TEXT`. Rather than special-case a `JSON`
6//! column type (and decode it differently per backend), `Json<T>` declares a
7//! plain `Text` column and (de)serializes `T` with `serde_json` at the column
8//! boundary. One representation — serialized JSON in a text column — works
9//! identically across MySQL, Postgres, SQLite, and D1, so a single generic
10//! adapter handles it everywhere. (If you have a native MySQL `JSON` column,
11//! migrate it to `TEXT`; the JSON text is byte-compatible.)
12//!
13//! ```ignore
14//! use rainier_orm::{Json, Entity};
15//! use serde::{Serialize, Deserialize};
16//!
17//! #[derive(Clone, Serialize, Deserialize)]
18//! struct Scopes(Vec<String>);
19//!
20//! #[derive(Entity)]
21//! struct Grant {
22//!     #[orm(pk, auto_increment)]
23//!     id: u64,
24//!     scopes: Json<Scopes>,            // stored as JSON text
25//!     extra: Option<Json<serde_json::Value>>,  // nullable, arbitrary JSON
26//! }
27//! ```
28
29use crate::{Error, Result, Row};
30use serde::{de::DeserializeOwned, Serialize};
31
32/// A `serde` value persisted as JSON text. Deref/`From` make it ergonomic to
33/// treat as the inner `T`.
34#[derive(Debug, Clone, PartialEq, Eq, Default)]
35pub struct Json<T>(pub T);
36
37impl<T> Json<T> {
38    pub fn into_inner(self) -> T {
39        self.0
40    }
41}
42
43impl<T> From<T> for Json<T> {
44    fn from(v: T) -> Self {
45        Json(v)
46    }
47}
48
49impl<T> core::ops::Deref for Json<T> {
50    type Target = T;
51    fn deref(&self) -> &T {
52        &self.0
53    }
54}
55
56impl<T> core::ops::DerefMut for Json<T> {
57    fn deref_mut(&mut self) -> &mut T {
58        &mut self.0
59    }
60}
61
62impl<T> crate::SqlType for Json<T> {
63    const COLUMN_TYPE: crate::ColumnType = crate::ColumnType::Text;
64}
65
66impl<T: Serialize> crate::ToColumn for Json<T> {
67    fn to_value(&self) -> sea_query::Value {
68        // Serialization failure is unexpected for a well-formed `T`; fall back
69        // to JSON null rather than panicking at the binding layer.
70        let s = serde_json::to_string(&self.0).unwrap_or_else(|_| "null".to_string());
71        sea_query::Value::String(Some(Box::new(s)))
72    }
73}
74
75impl<T: DeserializeOwned> crate::FromColumn for Json<T> {
76    fn from_column(row: &dyn Row, col: &str) -> Result<Self> {
77        let s =
78            row.get_string(col)?.ok_or_else(|| Error::msg(format!("column `{col}` was NULL")))?;
79        serde_json::from_str(&s)
80            .map(Json)
81            .map_err(|e| Error::msg(format!("column `{col}`: invalid JSON: {e}")))
82    }
83}