Skip to main content

pjson_rs_domain/value_objects/
json_data.rs

1//! Domain-specific JSON data value object
2//!
3//! Provides a Clean Architecture compliant representation of JSON data
4//! without depending on external serialization libraries in the domain layer.
5
6use crate::{DomainError, DomainResult};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fmt;
10
11/// Domain-specific representation of JSON-like data
12/// This replaces serde_json::Value to maintain Clean Architecture principles
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
14#[non_exhaustive]
15pub enum JsonData {
16    #[default]
17    /// Null value
18    Null,
19    /// Boolean value
20    Bool(bool),
21    /// Integer value
22    Integer(i64),
23    /// Float value (stored as f64 for simplicity)
24    Float(f64),
25    /// String value
26    String(String),
27    /// Array of JsonData values
28    Array(Vec<JsonData>),
29    /// Object with string keys and JsonData values
30    Object(HashMap<String, JsonData>),
31}
32
33impl JsonData {
34    /// Create a new null value
35    pub fn null() -> Self {
36        Self::Null
37    }
38
39    /// Create a new boolean value
40    pub fn bool(value: bool) -> Self {
41        Self::Bool(value)
42    }
43
44    /// Create a new integer value
45    pub fn integer(value: i64) -> Self {
46        Self::Integer(value)
47    }
48
49    /// Create a new float value.
50    ///
51    /// Returns `Err` when `value` is NaN or infinite. JSON (RFC 8259 §6) does not
52    /// allow non-finite numbers, so a `JsonData` containing one could never be
53    /// serialized to valid JSON.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use pjson_rs_domain::value_objects::JsonData;
59    ///
60    /// assert!(JsonData::float(3.14).is_ok());
61    /// assert!(JsonData::float(f64::NAN).is_err());
62    /// assert!(JsonData::float(f64::INFINITY).is_err());
63    /// ```
64    pub fn float(value: f64) -> DomainResult<Self> {
65        if value.is_nan() || value.is_infinite() {
66            return Err(DomainError::InvalidInput(
67                "JSON does not support NaN or infinite float values (RFC 8259 §6)".to_string(),
68            ));
69        }
70        Ok(Self::Float(value))
71    }
72
73    /// Create a new string value
74    pub fn string<S: Into<String>>(value: S) -> Self {
75        Self::String(value.into())
76    }
77
78    /// Create a new array value
79    pub fn array(values: Vec<JsonData>) -> Self {
80        Self::Array(values)
81    }
82
83    /// Create a new object value
84    pub fn object(values: HashMap<String, JsonData>) -> Self {
85        Self::Object(values)
86    }
87
88    /// Check if value is null
89    pub fn is_null(&self) -> bool {
90        matches!(self, Self::Null)
91    }
92
93    /// Check if value is boolean
94    pub fn is_bool(&self) -> bool {
95        matches!(self, Self::Bool(_))
96    }
97
98    /// Check if value is integer
99    pub fn is_integer(&self) -> bool {
100        matches!(self, Self::Integer(_))
101    }
102
103    /// Check if value is float
104    pub fn is_float(&self) -> bool {
105        matches!(self, Self::Float(_))
106    }
107
108    /// Check if value is number (integer or float)
109    pub fn is_number(&self) -> bool {
110        matches!(self, Self::Integer(_) | Self::Float(_))
111    }
112
113    /// Check if value is string
114    pub fn is_string(&self) -> bool {
115        matches!(self, Self::String(_))
116    }
117
118    /// Check if value is array
119    pub fn is_array(&self) -> bool {
120        matches!(self, Self::Array(_))
121    }
122
123    /// Check if value is object
124    pub fn is_object(&self) -> bool {
125        matches!(self, Self::Object(_))
126    }
127
128    /// Get boolean value if this is a boolean
129    pub fn as_bool(&self) -> Option<bool> {
130        match self {
131            Self::Bool(b) => Some(*b),
132            _ => None,
133        }
134    }
135
136    /// Get integer value if this is an integer
137    pub fn as_i64(&self) -> Option<i64> {
138        match self {
139            Self::Integer(i) => Some(*i),
140            _ => None,
141        }
142    }
143
144    /// Get float value if this is a float
145    pub fn as_f64(&self) -> Option<f64> {
146        match self {
147            Self::Float(f) => Some(*f),
148            Self::Integer(i) => Some(*i as f64),
149            _ => None,
150        }
151    }
152
153    /// Get string value if this is a string
154    pub fn as_str(&self) -> Option<&str> {
155        match self {
156            Self::String(s) => Some(s),
157            _ => None,
158        }
159    }
160
161    /// Get array value if this is an array
162    pub fn as_array(&self) -> Option<&Vec<JsonData>> {
163        match self {
164            Self::Array(arr) => Some(arr),
165            _ => None,
166        }
167    }
168
169    /// Get mutable array value if this is an array
170    pub fn as_array_mut(&mut self) -> Option<&mut Vec<JsonData>> {
171        match self {
172            Self::Array(arr) => Some(arr),
173            _ => None,
174        }
175    }
176
177    /// Get object value if this is an object
178    pub fn as_object(&self) -> Option<&HashMap<String, JsonData>> {
179        match self {
180            Self::Object(obj) => Some(obj),
181            _ => None,
182        }
183    }
184
185    /// Get mutable object value if this is an object
186    pub fn as_object_mut(&mut self) -> Option<&mut HashMap<String, JsonData>> {
187        match self {
188            Self::Object(obj) => Some(obj),
189            _ => None,
190        }
191    }
192
193    /// Get value by key (if this is an object)
194    pub fn get(&self, key: &str) -> Option<&JsonData> {
195        match self {
196            Self::Object(obj) => obj.get(key),
197            _ => None,
198        }
199    }
200
201    /// Get nested value by path (dot notation)
202    pub fn get_path(&self, path: &str) -> Option<&JsonData> {
203        let parts: Vec<&str> = path.split('.').collect();
204        let mut current = self;
205
206        for part in parts {
207            match current {
208                Self::Object(obj) => {
209                    current = obj.get(part)?;
210                }
211                _ => return None,
212            }
213        }
214
215        Some(current)
216    }
217
218    /// Set nested value by path (dot notation)
219    pub fn set_path(&mut self, path: &str, value: JsonData) -> bool {
220        let parts: Vec<&str> = path.split('.').collect();
221        if parts.is_empty() {
222            return false;
223        }
224
225        if parts.len() == 1 {
226            if let Self::Object(obj) = self {
227                obj.insert(parts[0].to_string(), value);
228                return true;
229            }
230            return false;
231        }
232
233        // Navigate to parent and create intermediate objects if needed
234        let mut current = self;
235        for part in &parts[..parts.len() - 1] {
236            match current {
237                Self::Object(obj) => {
238                    if !obj.contains_key(*part) {
239                        obj.insert(part.to_string(), Self::object(HashMap::new()));
240                    }
241                    current = obj
242                        .get_mut(*part)
243                        .expect("Key must exist as we just inserted it above");
244                }
245                _ => return false,
246            }
247        }
248
249        // Set final value
250        if let Self::Object(obj) = current {
251            obj.insert(parts[parts.len() - 1].to_string(), value);
252            true
253        } else {
254            false
255        }
256    }
257
258    /// Estimate memory size in bytes
259    pub fn memory_size(&self) -> usize {
260        match self {
261            Self::Null => 1,
262            Self::Bool(_) => 1,
263            Self::Integer(_) => 8,
264            Self::Float(_) => 8,
265            Self::String(s) => s.len() * 2, // UTF-16 estimation
266            Self::Array(arr) => 8 + arr.iter().map(|v| v.memory_size()).sum::<usize>(),
267            Self::Object(obj) => {
268                16 + obj
269                    .iter()
270                    .map(|(k, v)| k.len() * 2 + v.memory_size())
271                    .sum::<usize>()
272            }
273        }
274    }
275}
276
277impl fmt::Display for JsonData {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        match self {
280            Self::Null => write!(f, "null"),
281            Self::Bool(b) => write!(f, "{b}"),
282            Self::Integer(i) => write!(f, "{i}"),
283            Self::Float(float_val) => write!(f, "{float_val}"),
284            Self::String(s) => write!(f, "\"{s}\""),
285            Self::Array(arr) => {
286                write!(f, "[")?;
287                for (i, item) in arr.iter().enumerate() {
288                    if i > 0 {
289                        write!(f, ",")?;
290                    }
291                    write!(f, "{item}")?;
292                }
293                write!(f, "]")
294            }
295            Self::Object(obj) => {
296                write!(f, "{{")?;
297                for (i, (key, value)) in obj.iter().enumerate() {
298                    if i > 0 {
299                        write!(f, ",")?;
300                    }
301                    write!(f, "\"{key}\":{value}")?;
302                }
303                write!(f, "}}")
304            }
305        }
306    }
307}
308
309impl Eq for JsonData {}
310
311impl std::hash::Hash for JsonData {
312    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
313        match self {
314            Self::Null => 0u8.hash(state),
315            Self::Bool(b) => {
316                1u8.hash(state);
317                b.hash(state);
318            }
319            Self::Integer(i) => {
320                2u8.hash(state);
321                i.hash(state);
322            }
323            Self::Float(f) => {
324                3u8.hash(state);
325                // For floats, convert to bits for consistent hashing
326                f.to_bits().hash(state);
327            }
328            Self::String(s) => {
329                4u8.hash(state);
330                s.hash(state);
331            }
332            Self::Array(arr) => {
333                5u8.hash(state);
334                arr.hash(state);
335            }
336            Self::Object(obj) => {
337                6u8.hash(state);
338                // HashMap doesn't have deterministic iteration order,
339                // so we need to sort keys for consistent hashing
340                let mut pairs: Vec<_> = obj.iter().collect();
341                pairs.sort_by_key(|(k, _)| *k);
342                pairs.hash(state);
343            }
344        }
345    }
346}
347
348impl From<bool> for JsonData {
349    fn from(value: bool) -> Self {
350        Self::Bool(value)
351    }
352}
353
354impl From<i64> for JsonData {
355    fn from(value: i64) -> Self {
356        Self::Integer(value)
357    }
358}
359
360impl From<String> for JsonData {
361    fn from(value: String) -> Self {
362        Self::String(value)
363    }
364}
365
366impl From<&str> for JsonData {
367    fn from(value: &str) -> Self {
368        Self::String(value.to_string())
369    }
370}
371
372impl From<Vec<JsonData>> for JsonData {
373    fn from(value: Vec<JsonData>) -> Self {
374        Self::Array(value)
375    }
376}
377
378impl From<HashMap<String, JsonData>> for JsonData {
379    fn from(value: HashMap<String, JsonData>) -> Self {
380        Self::Object(value)
381    }
382}
383
384impl From<serde_json::Value> for JsonData {
385    fn from(value: serde_json::Value) -> Self {
386        match value {
387            serde_json::Value::Null => Self::Null,
388            serde_json::Value::Bool(b) => Self::Bool(b),
389            serde_json::Value::Number(n) => {
390                if let Some(i) = n.as_i64() {
391                    Self::Integer(i)
392                } else if let Some(f) = n.as_f64() {
393                    Self::Float(f)
394                } else {
395                    Self::Float(0.0) // fallback
396                }
397            }
398            serde_json::Value::String(s) => Self::String(s),
399            serde_json::Value::Array(arr) => {
400                let converted: Vec<JsonData> = arr.into_iter().map(JsonData::from).collect();
401                Self::Array(converted)
402            }
403            serde_json::Value::Object(obj) => {
404                let converted: HashMap<String, JsonData> = obj
405                    .into_iter()
406                    .map(|(k, v)| (k, JsonData::from(v)))
407                    .collect();
408                Self::Object(converted)
409            }
410        }
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    #[test]
419    fn test_json_data_creation() {
420        assert_eq!(JsonData::null(), JsonData::Null);
421        assert_eq!(JsonData::bool(true), JsonData::Bool(true));
422        assert_eq!(JsonData::float(42.0).unwrap(), JsonData::Float(42.0));
423        assert_eq!(
424            JsonData::string("hello"),
425            JsonData::String("hello".to_string())
426        );
427    }
428
429    #[test]
430    fn test_json_data_type_checks() {
431        assert!(JsonData::null().is_null());
432        assert!(JsonData::bool(true).is_bool());
433        assert!(JsonData::float(42.0).unwrap().is_number());
434        assert!(JsonData::string("hello").is_string());
435        assert!(JsonData::array(vec![]).is_array());
436        assert!(JsonData::object(HashMap::new()).is_object());
437    }
438
439    #[test]
440    fn test_json_data_conversions() {
441        assert_eq!(JsonData::bool(true).as_bool(), Some(true));
442        assert_eq!(JsonData::float(42.0).unwrap().as_f64(), Some(42.0));
443        assert_eq!(JsonData::integer(42).as_i64(), Some(42));
444        assert_eq!(JsonData::string("hello").as_str(), Some("hello"));
445    }
446
447    #[test]
448    fn test_path_operations() {
449        let mut data = JsonData::object(HashMap::new());
450
451        // Set nested path
452        assert!(data.set_path("user.name", JsonData::string("John")));
453        assert!(data.set_path("user.age", JsonData::integer(30)));
454
455        // Get nested path
456        assert_eq!(data.get_path("user.name").unwrap().as_str(), Some("John"));
457        assert_eq!(data.get_path("user.age").unwrap().as_i64(), Some(30));
458
459        // Non-existent path
460        assert!(data.get_path("user.email").is_none());
461    }
462
463    #[test]
464    fn test_memory_size() {
465        let data = JsonData::object(
466            [
467                ("name".to_string(), JsonData::string("John")),
468                ("age".to_string(), JsonData::integer(30)),
469            ]
470            .into_iter()
471            .collect(),
472        );
473
474        assert!(data.memory_size() > 0);
475    }
476
477    #[test]
478    fn test_display() {
479        let data = JsonData::object(
480            [
481                ("name".to_string(), JsonData::string("John")),
482                ("active".to_string(), JsonData::bool(true)),
483            ]
484            .into_iter()
485            .collect(),
486        );
487
488        let display = format!("{data}");
489        assert!(display.contains("name"));
490        assert!(display.contains("John"));
491    }
492}