Skip to main content

json_eval_rs/jsoneval/
getters.rs

1use super::JSONEval;
2use crate::jsoneval::path_utils;
3use crate::jsoneval::types::ReturnFormat;
4
5
6
7use serde_json::Value;
8use crate::time_block;
9
10
11impl JSONEval {
12    /// Check if a field is effectively hidden by checking its condition and all parents
13    /// Also checks for $layout.hideLayout.all on parents
14    pub(crate) fn is_effective_hidden(&self, schema_pointer: &str) -> bool {
15        let mut current_path = schema_pointer.to_string();
16
17        // Loop until broken (when we checked root)
18        loop {
19            // Check if current node exists
20            if let Some(schema_node) = self.evaluated_schema.pointer(&current_path) {
21                if let Value::Object(map) = schema_node {
22                    // 1. Check condition.hidden
23                    if let Some(Value::Object(condition)) = map.get("condition") {
24                        if let Some(Value::Bool(hidden)) = condition.get("hidden") {
25                            if *hidden {
26                                return true;
27                            }
28                        }
29                    }
30
31                    // 2. Check layout hidden state ($layout.hideLayout.all)
32                    if let Some(Value::Object(layout)) = map.get("$layout") {
33                        if let Some(Value::Object(hide_layout)) = layout.get("hideLayout") {
34                            if let Some(Value::Bool(all)) = hide_layout.get("all") {
35                                if *all {
36                                    return true;
37                                }
38                            }
39                        }
40                    }
41                }
42            }
43            
44            // If we just checked root, break
45            if current_path.is_empty() {
46                break;
47            }
48
49            // Move to parent
50            if let Some(last_slash_idx) = current_path.rfind('/') {
51                let parent_path_raw = &current_path[..last_slash_idx];
52                
53                if parent_path_raw.is_empty() {
54                    current_path = "".to_string();
55                    continue;
56                }
57                
58                // Handle path segments (strip /properties or /items)
59                if parent_path_raw.ends_with("/properties") {
60                    current_path = parent_path_raw[..parent_path_raw.len() - "/properties".len()].to_string();
61                } else if parent_path_raw.ends_with("/items") {
62                     current_path = parent_path_raw[..parent_path_raw.len() - "/items".len()].to_string();
63                } else {
64                     current_path = parent_path_raw.to_string();
65                }
66            } else {
67                break;
68            }
69        }
70
71        false
72    }
73    
74    /// Prune hidden values from data object recursively
75    fn prune_hidden_values(&self, data: &mut Value, current_path: &str) {
76        if let Value::Object(map) = data {
77            // Collect keys to remove to avoid borrow checker issues
78            let mut keys_to_remove = Vec::new();
79            
80            for (key, value) in map.iter_mut() {
81                // Skip special keys
82                if key == "$params" || key == "$context" {
83                    continue;
84                }
85                
86                // Construct schema path for this key
87                // For root fields: /properties/key
88                // For nested fields: current_path/properties/key
89                let schema_path = if current_path.is_empty() {
90                    format!("/properties/{}", key)
91                } else {
92                    format!("{}/properties/{}", current_path, key)
93                };
94                
95                // Check if hidden
96                if self.is_effective_hidden(&schema_path) {
97                    keys_to_remove.push(key.clone());
98                } else {
99                    // Recurse if object
100                    if value.is_object() {
101                        self.prune_hidden_values(value, &schema_path);
102                    }
103                }
104            }
105            
106            // Remove hidden keys
107            for key in keys_to_remove {
108                map.remove(&key);
109            }
110        }
111    }
112
113    /// Get the evaluated schema with optional layout resolution.
114    ///
115    /// # Arguments
116    ///
117    /// * `skip_layout` - Whether to skip layout resolution.
118    ///
119    /// # Returns
120    ///
121    /// The evaluated schema as a JSON value.
122    pub fn get_evaluated_schema(&mut self, skip_layout: bool) -> Value {
123        time_block!("get_evaluated_schema()", {
124            if !skip_layout {
125                if let Err(e) = self.resolve_layout(false) {
126                    eprintln!("Warning: Layout resolution failed in get_evaluated_schema: {}", e);
127                }
128            }
129            self.evaluated_schema.clone()
130        })
131    }
132
133
134
135    /// Get specific schema value by path
136    pub fn get_schema_value_by_path(&self, path: &str) -> Option<Value> {
137        let pointer_path = path_utils::dot_notation_to_schema_pointer(path);
138        self.evaluated_schema.pointer(&pointer_path.trim_start_matches('#')).cloned()
139    }
140
141    /// Get all schema values (data view)
142    /// Mutates internal data state by overriding with values from value evaluations
143    /// This corresponds to subform.get_schema_value() usage
144    pub fn get_schema_value(&mut self) -> Value {
145        // Start with current authoritative data from eval_data
146        let mut current_data = self.eval_data.data().clone();
147
148        // Ensure it's an object
149        if !current_data.is_object() {
150            current_data = Value::Object(serde_json::Map::new());
151        }
152
153        // Strip $params and $context from data
154        if let Some(obj) = current_data.as_object_mut() {
155            obj.remove("$params");
156            obj.remove("$context");
157        }
158        
159        // Prune hidden values from current_data (to remove user input in hidden fields)
160        self.prune_hidden_values(&mut current_data, "");
161
162        // Override data with values from value evaluations
163        // We use value_evaluations which stores the paths of fields with .value
164        for eval_key in self.value_evaluations.iter() {
165            let clean_key = eval_key.replace('#', "");
166
167            // Exclude rules.*.value, options.*.value, and $params
168            if clean_key.starts_with("/$params")
169                || (clean_key.ends_with("/value")
170                    && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
171            {
172                continue;
173            }
174
175            let path = clean_key.replace("/properties", "").replace("/value", "");
176            
177            // Check if field is effectively hidden
178            // Schema path is clean_key without /value
179            let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
180            if self.is_effective_hidden(schema_path) {
181                continue;
182            }
183
184            // Get the value from evaluated_schema
185            let value = match self.evaluated_schema.pointer(&clean_key) {
186                Some(v) => v.clone(),
187                None => continue,
188            };
189
190            // Parse the path and create nested structure as needed
191            let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
192
193            if path_parts.is_empty() {
194                continue;
195            }
196
197            // Navigate/create nested structure
198            let mut current = &mut current_data;
199            for (i, part) in path_parts.iter().enumerate() {
200                let is_last = i == path_parts.len() - 1;
201
202                if is_last {
203                    // Set the value at the final key
204                    if let Some(obj) = current.as_object_mut() {
205                        obj.insert(part.to_string(), crate::utils::clean_float_noise(value.clone()));
206                    }
207                } else {
208                    // Ensure current is an object, then navigate/create intermediate objects
209                    if let Some(obj) = current.as_object_mut() {
210                        // Use raw entry API or standard entry if possible, but borrowing is tricky
211                        // We need to re-borrow `current` for the next iteration
212                        // Since `entry` API consumes check, we might need a different approach or careful usage
213                        
214                        // Check presence first to avoid borrow issues if simpler
215                        if !obj.contains_key(*part) {
216                            obj.insert((*part).to_string(), Value::Object(serde_json::Map::new()));
217                        }
218                        
219                        current = obj.get_mut(*part).unwrap();
220                    } else {
221                        // Skip this path if current is not an object and can't be made into one
222                        break;
223                    }
224                }
225            }
226        }
227        
228        // Update self.data to persist the view changes (matching backup behavior)
229        self.data = current_data.clone();
230        
231        crate::utils::clean_float_noise(current_data)
232    }
233
234    /// Get all schema values as array of path-value pairs
235    /// Returns [{path: "", value: ""}, ...]
236    ///
237    /// # Returns
238    ///
239    /// Array of objects containing path (dotted notation) and value pairs from value evaluations
240    pub fn get_schema_value_array(&self) -> Value {
241        let mut result = Vec::new();
242        
243        for eval_key in self.value_evaluations.iter() {
244            let clean_key = eval_key.replace('#', "");
245
246            // Exclude rules.*.value, options.*.value, and $params
247            if clean_key.starts_with("/$params")
248                || (clean_key.ends_with("/value")
249                    && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
250            {
251                continue;
252            }
253            
254            // Check if field is effectively hidden
255            let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
256            if self.is_effective_hidden(schema_path) {
257                continue;
258            }
259
260            // Convert JSON pointer to dotted notation
261            let dotted_path = clean_key
262                .replace("/properties", "")
263                .replace("/value", "")
264                .trim_start_matches('/')
265                .replace('/', ".");
266
267            if dotted_path.is_empty() {
268                continue;
269            }
270
271            // Get the value from evaluated_schema
272            let value = match self.evaluated_schema.pointer(&clean_key) {
273                Some(v) => crate::utils::clean_float_noise(v.clone()),
274                None => continue,
275            };
276
277            // Create {path, value} object
278            let mut item = serde_json::Map::new();
279            item.insert("path".to_string(), Value::String(dotted_path));
280            item.insert("value".to_string(), value);
281            result.push(Value::Object(item));
282        }
283        
284        Value::Array(result)
285    }
286
287    /// Get all schema values as object with dotted path keys
288    /// Returns {path: value, ...}
289    ///
290    /// # Returns
291    ///
292    /// Flat object with dotted notation paths as keys and evaluated values
293    pub fn get_schema_value_object(&self) -> Value {
294        let mut result = serde_json::Map::new();
295        
296        for eval_key in self.value_evaluations.iter() {
297            let clean_key = eval_key.replace('#', "");
298
299            // Exclude rules.*.value, options.*.value, and $params
300            if clean_key.starts_with("/$params")
301                || (clean_key.ends_with("/value")
302                    && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
303            {
304                continue;
305            }
306            
307            // Check if field is effectively hidden
308            let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
309            if self.is_effective_hidden(schema_path) {
310                continue;
311            }
312
313            // Convert JSON pointer to dotted notation
314            let dotted_path = clean_key
315                .replace("/properties", "")
316                .replace("/value", "")
317                .trim_start_matches('/')
318                .replace('/', ".");
319
320            if dotted_path.is_empty() {
321                continue;
322            }
323
324            // Get the value from evaluated_schema
325            let value = match self.evaluated_schema.pointer(&clean_key) {
326                Some(v) => crate::utils::clean_float_noise(v.clone()),
327                None => continue,
328            };
329
330            result.insert(dotted_path, value);
331        }
332        
333        Value::Object(result)
334    }
335
336    /// Get evaluated schema without $params
337    pub fn get_evaluated_schema_without_params(&mut self, skip_layout: bool) -> Value {
338        let mut schema = self.get_evaluated_schema(skip_layout);
339        if let Value::Object(ref mut map) = schema {
340            map.remove("$params");
341        }
342        schema
343    }
344
345    /// Get evaluated schema as MessagePack bytes
346    pub fn get_evaluated_schema_msgpack(&mut self, skip_layout: bool) -> Result<Vec<u8>, String> {
347        let schema = self.get_evaluated_schema(skip_layout);
348        rmp_serde::to_vec(&schema).map_err(|e| format!("MessagePack serialization failed: {}", e))
349    }
350
351    /// Get value from evaluated schema by path
352    pub fn get_evaluated_schema_by_path(&mut self, path: &str, skip_layout: bool) -> Option<Value> {
353        if !skip_layout {
354            if let Err(e) = self.resolve_layout(false) {
355                eprintln!("Warning: Layout resolution failed in get_evaluated_schema_by_path: {}", e);
356            }
357        }
358        self.get_schema_value_by_path(path)
359    }
360
361    /// Get evaluated schema parts by multiple paths
362    pub fn get_evaluated_schema_by_paths(
363        &mut self,
364        paths: &[String],
365        skip_layout: bool,
366        format: Option<ReturnFormat>,
367    ) -> Value {
368        if !skip_layout {
369            if let Err(e) = self.resolve_layout(false) {
370                eprintln!("Warning: Layout resolution failed in get_evaluated_schema_by_paths: {}", e);
371            }
372        }
373
374        match format.unwrap_or(ReturnFormat::Nested) {
375            ReturnFormat::Nested => {
376                let mut result = Value::Object(serde_json::Map::new());
377                for path in paths {
378                    if let Some(val) = self.get_schema_value_by_path(path) {
379                         // Insert into result object at proper path nesting
380                         Self::insert_at_path(&mut result, path, val);
381                    }
382                }
383                result
384            }
385            ReturnFormat::Flat => {
386                 let mut result = serde_json::Map::new();
387                 for path in paths {
388                    if let Some(val) = self.get_schema_value_by_path(path) {
389                        result.insert(path.clone(), val);
390                    }
391                }
392                Value::Object(result)
393            }
394            ReturnFormat::Array => {
395                 let mut result = Vec::new();
396                 for path in paths {
397                    if let Some(val) = self.get_schema_value_by_path(path) {
398                        result.push(val);
399                    } else {
400                        result.push(Value::Null);
401                    }
402                }
403                Value::Array(result)
404            }
405        }
406    }
407
408    /// Get original (unevaluated) schema by path
409    pub fn get_schema_by_path(&self, path: &str) -> Option<Value> {
410        let pointer_path = path_utils::dot_notation_to_schema_pointer(path);
411        self.schema.pointer(&pointer_path.trim_start_matches('#')).cloned()
412    }
413
414    /// Get original schema by multiple paths
415    pub fn get_schema_by_paths(
416        &self,
417        paths: &[String],
418        format: Option<ReturnFormat>,
419    ) -> Value {
420        match format.unwrap_or(ReturnFormat::Nested) {
421            ReturnFormat::Nested => {
422                let mut result = Value::Object(serde_json::Map::new());
423                for path in paths {
424                    if let Some(val) = self.get_schema_by_path(path) {
425                         Self::insert_at_path(&mut result, path, val);
426                    }
427                }
428                result
429            }
430            ReturnFormat::Flat => {
431                 let mut result = serde_json::Map::new();
432                 for path in paths {
433                    if let Some(val) = self.get_schema_by_path(path) {
434                        result.insert(path.clone(), val);
435                    }
436                }
437                Value::Object(result)
438            }
439            ReturnFormat::Array => {
440                 let mut result = Vec::new();
441                 for path in paths {
442                    if let Some(val) = self.get_schema_by_path(path) {
443                        result.push(val);
444                    } else {
445                        result.push(Value::Null);
446                    }
447                }
448                Value::Array(result)
449            }
450        }
451    }
452
453    /// Helper to insert value into nested object at dotted path
454    pub(crate) fn insert_at_path(root: &mut Value, path: &str, value: Value) {
455        let parts: Vec<&str> = path.split('.').collect();
456        let mut current = root;
457        
458        for (i, part) in parts.iter().enumerate() {
459            if i == parts.len() - 1 {
460                // Last part - set value
461                if let Value::Object(map) = current {
462                    map.insert(part.to_string(), value);
463                    return; // Done
464                }
465            } else {
466                // Intermediate part - traverse or create
467                // We need to temporarily take the value or use raw pointer manipulation?
468                // serde_json pointer is read-only or requires mutable reference
469                
470                 if !current.is_object() {
471                     *current = Value::Object(serde_json::Map::new());
472                 }
473                 
474                 if let Value::Object(map) = current {
475                     if !map.contains_key(*part) {
476                         map.insert(part.to_string(), Value::Object(serde_json::Map::new()));
477                     }
478                     current = map.get_mut(*part).unwrap();
479                 }
480            }
481        }
482    }
483    
484    /// Flatten a nested object key-value pair to dotted keys
485    pub fn flatten_object(prefix: &str, value: &Value, result: &mut serde_json::Map<String, Value>) {
486        match value {
487            Value::Object(map) => {
488                for (k, v) in map {
489                     let new_key = if prefix.is_empty() {
490                         k.clone()
491                     } else {
492                         format!("{}.{}", prefix, k)
493                     };
494                     Self::flatten_object(&new_key, v, result);
495                }
496            }
497            _ => {
498                result.insert(prefix.to_string(), value.clone());
499            }
500        }
501    }
502
503    pub fn convert_to_format(value: Value, format: ReturnFormat) -> Value {
504         match format {
505             ReturnFormat::Nested => value,
506             ReturnFormat::Flat => {
507                 let mut result = serde_json::Map::new();
508                 Self::flatten_object("", &value, &mut result);
509                 Value::Object(result)
510             }
511             ReturnFormat::Array => {
512                 // Convert object values to array? Only if source was object?
513                 // Or flattened values?
514                 // Usually converting to array disregards keys.
515                 if let Value::Object(map) = value {
516                     Value::Array(map.values().cloned().collect())
517                 } else if let Value::Array(arr) = value {
518                     Value::Array(arr)
519                 } else {
520                     Value::Array(vec![value])
521                 }
522             }
523         }
524    }
525}