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