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