Skip to main content

json_eval_rs/jsoneval/
getters.rs

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