Skip to main content

linkrs_core/value/
json.rs

1//! JSON/JSONB Types - Graph Database JSON Support
2//!
3//! This module provides JSON and JSONB types for storing semi-structured data.
4//!
5//! ## JSON vs JSONB
6//!
7//! - JSON: Text format, preserves original formatting (whitespace, key order)
8//! - JSONB: Binary format, parsed and normalized for better query performance
9
10use serde::{Deserialize, Serialize};
11use serde_json::Value as JsonValue;
12use std::hash::{Hash, Hasher};
13
14/// JSON Error Type
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum JsonError {
17    InvalidJson(String),
18    InvalidPath(String),
19    TypeMismatch { expected: String, actual: String },
20}
21
22impl std::fmt::Display for JsonError {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            JsonError::InvalidJson(msg) => write!(f, "Invalid JSON: {}", msg),
26            JsonError::InvalidPath(path) => write!(f, "Invalid JSON path: {}", path),
27            JsonError::TypeMismatch { expected, actual } => {
28                write!(f, "Type mismatch: expected {}, got {}", expected, actual)
29            }
30        }
31    }
32}
33
34impl std::error::Error for JsonError {}
35
36/// JSON Type - Text Storage Format
37///
38/// Features:
39/// - Preserves original text format (including whitespace, key order)
40/// - No parsing required on write, better write performance
41/// - Parsing required on query
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Json {
44    /// Original JSON text
45    text: String,
46    /// Cached parse result (optional, for improved query performance)
47    #[serde(skip)]
48    cached_value: Option<JsonValue>,
49}
50
51/// JSONB Type - Binary Storage Format
52///
53/// Features:
54/// - Stores parsed binary format
55/// - Parsing and validation required on write
56/// - Better query performance
57/// - Supports GIN index creation
58///
59/// Serialization note: `JsonValue` (`serde_json::Value`) is a content-tagged
60/// enum that postcard (used by WAL/undo persistence) cannot decode. JsonB is
61/// therefore serialized as its JSON string, which round-trips losslessly.
62#[derive(Debug, Clone)]
63pub struct JsonB {
64    value: JsonValue,
65}
66
67impl Serialize for JsonB {
68    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
69        serializer.serialize_str(&self.value.to_string())
70    }
71}
72
73impl<'de> Deserialize<'de> for JsonB {
74    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
75        let text = String::deserialize(deserializer)?;
76        serde_json::from_str(&text)
77            .map(Self::from_value)
78            .map_err(serde::de::Error::custom)
79    }
80}
81
82impl Json {
83    /// Create JSON from string
84    pub fn parse(text: &str) -> Result<Self, JsonError> {
85        // Optional: Validate JSON format
86        let value: JsonValue =
87            serde_json::from_str(text).map_err(|e| JsonError::InvalidJson(e.to_string()))?;
88
89        Ok(Self {
90            text: text.to_string(),
91            cached_value: Some(value),
92        })
93    }
94
95    /// Create from JsonValue
96    pub fn from_value(value: JsonValue) -> Self {
97        let text = value.to_string();
98        Self {
99            text,
100            cached_value: Some(value),
101        }
102    }
103
104    /// Get raw text
105    pub fn as_str(&self) -> &str {
106        &self.text
107    }
108
109    /// Get parsed value (with caching)
110    pub fn to_value(&self) -> Result<JsonValue, JsonError> {
111        if let Some(ref value) = self.cached_value {
112            return Ok(value.clone());
113        }
114
115        serde_json::from_str(&self.text).map_err(|e| JsonError::InvalidJson(e.to_string()))
116    }
117
118    /// Get value at specified path
119    /// Path format: "key1.key2[0].key3"
120    pub fn get_path(&self, path: &str) -> Result<Option<JsonValue>, JsonError> {
121        let value = self.to_value()?;
122        Ok(get_json_path(&value, path))
123    }
124
125    /// Convert to JsonB
126    pub fn to_jsonb(&self) -> Result<JsonB, JsonError> {
127        let value = self.to_value()?;
128        Ok(JsonB::from_value(value))
129    }
130
131    /// Extract JSON path value, return new Json
132    pub fn extract(&self, path: &str) -> Result<Json, JsonError> {
133        let json_value = self.get_path(path)?;
134        Ok(Json::from_value(json_value.unwrap_or(JsonValue::Null)))
135    }
136
137    /// Estimate memory usage
138    pub fn estimated_size(&self) -> usize {
139        std::mem::size_of::<Self>() + self.text.capacity()
140    }
141}
142
143impl PartialEq for Json {
144    fn eq(&self, other: &Self) -> bool {
145        // Compare parsed values, not text
146        match (self.to_value(), other.to_value()) {
147            (Ok(a), Ok(b)) => a == b,
148            _ => false,
149        }
150    }
151}
152
153impl Eq for Json {}
154
155impl Hash for Json {
156    fn hash<H: Hasher>(&self, state: &mut H) {
157        // Use parsed value for hashing
158        if let Ok(value) = self.to_value() {
159            hash_json_value(&value, state);
160        }
161    }
162}
163
164// Json and JsonB use serde for serialization, postcard will use serde integration
165
166impl JsonB {
167    /// Create JSONB from string (must validate)
168    pub fn parse(text: &str) -> Result<Self, JsonError> {
169        let value: JsonValue =
170            serde_json::from_str(text).map_err(|e| JsonError::InvalidJson(e.to_string()))?;
171
172        Ok(Self::from_value(value))
173    }
174
175    /// Create from JsonValue
176    pub fn from_value(value: JsonValue) -> Self {
177        // Normalize JSON (sort keys, remove whitespace)
178        let normalized = normalize_json(value);
179
180        Self { value: normalized }
181    }
182
183    /// Get parsed value
184    pub fn as_value(&self) -> &JsonValue {
185        &self.value
186    }
187
188    /// Convert to text format
189    pub fn to_json_string(&self) -> String {
190        self.value.to_string()
191    }
192
193    /// Get value at specified path
194    pub fn get_path(&self, path: &str) -> Option<&JsonValue> {
195        get_json_path_ref(&self.value, path)
196    }
197
198    /// Convert to Json
199    pub fn to_json(&self) -> Json {
200        Json::from_value(self.value.clone())
201    }
202
203    /// Check if contains specified key
204    pub fn contains_key(&self, key: &str) -> bool {
205        matches!(self.value, JsonValue::Object(ref map) if map.contains_key(key))
206    }
207
208    /// Get object key count
209    pub fn key_count(&self) -> usize {
210        match self.value {
211            JsonValue::Object(ref map) => map.len(),
212            _ => 0,
213        }
214    }
215
216    /// Get array length
217    pub fn array_len(&self) -> Option<usize> {
218        match self.value {
219            JsonValue::Array(ref arr) => Some(arr.len()),
220            _ => None,
221        }
222    }
223
224    /// Extract JSON path value, return new JsonB
225    pub fn extract(&self, path: &str) -> JsonB {
226        let json_value = self.get_path(path).cloned().unwrap_or(JsonValue::Null);
227        JsonB::from_value(json_value)
228    }
229
230    /// Estimate memory usage
231    pub fn estimated_size(&self) -> usize {
232        std::mem::size_of::<Self>() + estimate_json_size(&self.value)
233    }
234}
235
236impl PartialEq for JsonB {
237    fn eq(&self, other: &Self) -> bool {
238        self.value == other.value
239    }
240}
241
242impl Eq for JsonB {}
243
244impl Hash for JsonB {
245    fn hash<H: Hasher>(&self, state: &mut H) {
246        hash_json_value(&self.value, state);
247    }
248}
249
250impl Ord for JsonB {
251    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
252        // JSONB comparison rules: by type priority, then by value
253        compare_json_values(&self.value, &other.value)
254    }
255}
256
257impl PartialOrd for JsonB {
258    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
259        Some(self.cmp(other))
260    }
261}
262
263// Json and JsonB use serde for serialization, postcard will use serde integration
264
265/// Normalize JSON value (for JSONB)
266fn normalize_json(value: JsonValue) -> JsonValue {
267    match value {
268        JsonValue::Object(map) => {
269            // Sort keys and recursively normalize
270            let mut entries: Vec<_> = map.into_iter().collect();
271            entries.sort_by(|a, b| a.0.cmp(&b.0));
272            let normalized: serde_json::Map<String, JsonValue> = entries
273                .into_iter()
274                .map(|(k, v)| (k, normalize_json(v)))
275                .collect();
276            JsonValue::Object(normalized)
277        }
278        JsonValue::Array(arr) => JsonValue::Array(arr.into_iter().map(normalize_json).collect()),
279        // Other types remain unchanged
280        other => other,
281    }
282}
283
284/// Get JSON path value
285fn get_json_path(value: &JsonValue, path: &str) -> Option<JsonValue> {
286    let parts: Vec<&str> = path.split('.').collect();
287    let mut current = value;
288
289    for part in parts {
290        // Handle array index, e.g., "items[0]"
291        if let Some(idx_start) = part.find('[') {
292            let key = &part[..idx_start];
293            let idx_end = part.find(']').unwrap_or(part.len());
294            let idx: usize = part[idx_start + 1..idx_end].parse().ok()?;
295
296            if !key.is_empty() {
297                current = current.get(key)?;
298            }
299            current = current.get(idx)?;
300        } else {
301            current = current.get(part)?;
302        }
303    }
304
305    Some(current.clone())
306}
307
308/// Get JSON path value (reference version)
309fn get_json_path_ref<'a>(value: &'a JsonValue, path: &str) -> Option<&'a JsonValue> {
310    let parts: Vec<&str> = path.split('.').collect();
311    let mut current = value;
312
313    for part in parts {
314        if let Some(idx_start) = part.find('[') {
315            let key = &part[..idx_start];
316            let idx_end = part.find(']').unwrap_or(part.len());
317            let idx: usize = part[idx_start + 1..idx_end].parse().ok()?;
318
319            if !key.is_empty() {
320                current = current.get(key)?;
321            }
322            current = current.get(idx)?;
323        } else {
324            current = current.get(part)?;
325        }
326    }
327
328    Some(current)
329}
330
331/// Hash JSON value
332fn hash_json_value<H: Hasher>(value: &JsonValue, state: &mut H) {
333    match value {
334        JsonValue::Null => 0u8.hash(state),
335        JsonValue::Bool(b) => {
336            1u8.hash(state);
337            b.hash(state);
338        }
339        JsonValue::Number(n) => {
340            2u8.hash(state);
341            n.to_string().hash(state);
342        }
343        JsonValue::String(s) => {
344            3u8.hash(state);
345            s.hash(state);
346        }
347        JsonValue::Array(arr) => {
348            4u8.hash(state);
349            for item in arr {
350                hash_json_value(item, state);
351            }
352        }
353        JsonValue::Object(map) => {
354            5u8.hash(state);
355            for (k, v) in map {
356                k.hash(state);
357                hash_json_value(v, state);
358            }
359        }
360    }
361}
362
363/// Compare two JSON values
364fn compare_json_values(a: &JsonValue, b: &JsonValue) -> std::cmp::Ordering {
365    use std::cmp::Ordering;
366
367    // Type priority: Null < Bool < Number < String < Array < Object
368    let type_priority = |v: &JsonValue| match v {
369        JsonValue::Null => 0,
370        JsonValue::Bool(_) => 1,
371        JsonValue::Number(_) => 2,
372        JsonValue::String(_) => 3,
373        JsonValue::Array(_) => 4,
374        JsonValue::Object(_) => 5,
375    };
376
377    let priority_a = type_priority(a);
378    let priority_b = type_priority(b);
379
380    match priority_a.cmp(&priority_b) {
381        Ordering::Equal => match (a, b) {
382            (JsonValue::Null, JsonValue::Null) => Ordering::Equal,
383            (JsonValue::Bool(a), JsonValue::Bool(b)) => a.cmp(b),
384            (JsonValue::Number(a), JsonValue::Number(b)) => {
385                // Try comparing as integers, otherwise as floats
386                if let (Some(a_i), Some(b_i)) = (a.as_i64(), b.as_i64()) {
387                    a_i.cmp(&b_i)
388                } else if let (Some(a_f), Some(b_f)) = (a.as_f64(), b.as_f64()) {
389                    a_f.partial_cmp(&b_f).unwrap_or(Ordering::Equal)
390                } else {
391                    a.to_string().cmp(&b.to_string())
392                }
393            }
394            (JsonValue::String(a), JsonValue::String(b)) => a.cmp(b),
395            (JsonValue::Array(a), JsonValue::Array(b)) => a
396                .iter()
397                .zip(b.iter())
398                .map(|(x, y)| compare_json_values(x, y))
399                .find(|&ord| ord != Ordering::Equal)
400                .unwrap_or_else(|| a.len().cmp(&b.len())),
401            (JsonValue::Object(a), JsonValue::Object(b)) => {
402                let a_keys: Vec<_> = a.keys().collect();
403                let b_keys: Vec<_> = b.keys().collect();
404
405                match a_keys.cmp(&b_keys) {
406                    Ordering::Equal => a_keys
407                        .iter()
408                        .map(|k| compare_json_values(&a[*k], &b[*k]))
409                        .find(|&ord| ord != Ordering::Equal)
410                        .unwrap_or(Ordering::Equal),
411                    other => other,
412                }
413            }
414            _ => Ordering::Equal, // Different types already handled by priority
415        },
416        other => other,
417    }
418}
419
420/// Estimate JSON value memory usage
421fn estimate_json_size(value: &JsonValue) -> usize {
422    match value {
423        JsonValue::Null => 0,
424        JsonValue::Bool(_) => 1,
425        JsonValue::Number(n) => n.to_string().len(),
426        JsonValue::String(s) => s.len(),
427        JsonValue::Array(arr) => {
428            arr.iter().map(estimate_json_size).sum::<usize>()
429                + arr.len() * std::mem::size_of::<JsonValue>()
430        }
431        JsonValue::Object(map) => {
432            map.iter()
433                .map(|(k, v)| k.len() + estimate_json_size(v))
434                .sum::<usize>()
435                + map.len() * std::mem::size_of::<(String, JsonValue)>()
436        }
437    }
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn test_json_from_str() {
446        let json = Json::parse(r#"{"name": "test", "value": 123}"#).unwrap();
447        assert!(json.get_path("name").unwrap().is_some());
448        assert!(json.get_path("value").unwrap().is_some());
449    }
450
451    #[test]
452    fn test_jsonb_from_str() {
453        let jsonb = JsonB::parse(r#"{"name": "test", "value": 123}"#).unwrap();
454        assert!(jsonb.contains_key("name"));
455        assert!(jsonb.contains_key("value"));
456        assert_eq!(jsonb.key_count(), 2);
457    }
458
459    #[test]
460    fn test_json_path() {
461        let json = Json::parse(r#"{"items": [{"name": "a"}, {"name": "b"}]}"#).unwrap();
462        let value = json.get_path("items[0].name").unwrap();
463        assert!(value.is_some());
464    }
465
466    #[test]
467    fn test_json_to_jsonb() {
468        let json = Json::parse(r#"{"test": 123}"#).unwrap();
469        let jsonb = json.to_jsonb().unwrap();
470        assert!(jsonb.contains_key("test"));
471    }
472
473    #[test]
474    fn test_jsonb_comparison() {
475        let a = JsonB::parse("1").unwrap();
476        let b = JsonB::parse("2").unwrap();
477        assert!(a < b);
478    }
479
480    #[test]
481    fn test_jsonb_equality() {
482        let a = JsonB::parse(r#"{"b": 1, "a": 2}"#).unwrap();
483        let b = JsonB::parse(r#"{"a": 2, "b": 1}"#).unwrap();
484        // JSONB normalizes key order, so these should be equal
485        assert_eq!(a, b);
486    }
487}