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