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