json_eval_rs/rlogic/evaluator/
helpers.rs

1use serde_json::{Value, Number};
2use crate::path_utils;
3
4/// Convert f64 to JSON number
5#[inline(always)]
6pub fn f64_to_json(f: f64, safe_nan_handling: bool) -> Value {
7    if f.is_finite() {
8        Number::from_f64(f).map(Value::Number).unwrap_or(Value::Null)
9    } else if safe_nan_handling {
10        Value::Number(Number::from_f64(0.0).unwrap())
11    } else {
12        Value::Null
13    }
14}
15
16/// Convert JSON value to f64
17#[inline(always)]
18pub fn to_f64(value: &Value) -> f64 {
19    match value {
20        Value::Number(n) => n.as_f64().unwrap_or(0.0),
21        Value::Bool(true) => 1.0,
22        Value::Bool(false) => 0.0,
23        Value::String(s) => s.parse::<f64>().unwrap_or(0.0),
24        Value::Array(arr) => {
25            if arr.len() == 1 {
26                to_f64(&arr[0])
27            } else {
28                0.0
29            }
30        }
31        _ => 0.0,
32    }
33}
34
35/// Legacy to_number for f64 (only used for Power operations)
36#[inline]
37pub fn to_number(value: &Value) -> f64 {
38    to_f64(value)
39}
40
41/// Helper to parse string to f64 (empty string = 0.0)
42#[inline]
43pub fn parse_string_to_f64(s: &str) -> Option<f64> {
44    if s.is_empty() {
45        Some(0.0)
46    } else {
47        s.parse::<f64>().ok()
48    }
49}
50
51/// Convert value to string
52#[inline]
53pub fn to_string(value: &Value) -> String {
54    match value {
55        Value::Null => String::new(),
56        Value::Bool(b) => b.to_string(),
57        Value::Number(n) => {
58            // JavaScript-like number to string conversion:
59            // Integer-valued numbers should not have decimal point
60            if let Some(f) = n.as_f64() {
61                if f.is_finite() && f == f.floor() && f.abs() < 1e15 {
62                    // It's an integer value, format without decimal
63                    format!("{}", f as i64)
64                } else {
65                    n.to_string()
66                }
67            } else {
68                n.to_string()
69            }
70        }
71        Value::String(s) => s.clone(),
72        Value::Array(_) | Value::Object(_) => value.to_string(),
73    }
74}
75
76/// Use centralized path normalization for consistent $ref/var handling
77#[inline]
78pub fn normalize_ref_path(path: &str) -> String {
79    path_utils::normalize_to_json_pointer(path)
80}
81
82/// OPTIMIZED: Fast variable access - paths are pre-normalized during compilation
83/// This method is now only used for legacy/fallback cases
84#[inline(always)]
85pub fn get_var<'a>(data: &'a Value, name: &str) -> Option<&'a Value> {
86    if name.is_empty() {
87        return Some(data);
88    }
89
90    // OPTIMIZED: Assume path is already normalized (from compilation)
91    // Direct JSON pointer access without re-normalization
92    path_utils::get_value_by_pointer_without_properties(data, name)
93}
94
95/// Get variable with layered context (primary first, then fallback)
96#[inline]
97pub fn get_var_layered<'a>(primary: &'a Value, fallback: &'a Value, name: &str) -> Option<&'a Value> {
98    get_var(primary, name).or_else(|| get_var(fallback, name))
99}
100
101/// Check if key is missing (null or not present)
102#[inline]
103pub fn is_key_missing(data: &Value, key: &str) -> bool {
104    if key.is_empty() {
105        return false;
106    }
107    let pointer = path_utils::normalize_to_json_pointer(key);
108    if pointer.is_empty() {
109        return false;
110    }
111    get_var(data, &pointer).map(|v| v.is_null()).unwrap_or(true)
112}
113
114/// Check if value is truthy
115#[inline]
116pub fn is_truthy(value: &Value) -> bool {
117    match value {
118        Value::Null => false,
119        Value::Bool(b) => *b,
120        Value::Number(n) => n.as_f64().unwrap_or(0.0) != 0.0,
121        Value::String(s) => !s.is_empty(),
122        Value::Array(arr) => !arr.is_empty(),
123        Value::Object(_) => true,
124    }
125}
126
127/// Check if value is null-like (Null, empty string, or NaN)
128#[inline]
129pub fn is_null_like(value: &Value) -> bool {
130    match value {
131        Value::Null => true,
132        Value::String(s) if s.is_empty() => true,
133        Value::Number(n) if n.is_f64() && n.as_f64().unwrap().is_nan() => true,
134        _ => false
135    }
136}
137
138/// Build ISO date string from NaiveDate
139#[inline]
140pub fn build_iso_date_string(date: chrono::NaiveDate) -> String {
141    let mut result = String::with_capacity(24);
142    result.push_str(&date.format("%Y-%m-%d").to_string());
143    result.push_str("T00:00:00.000Z");
144    result
145}
146
147/// Compare two values as numbers
148#[inline]
149pub fn compare(a: &Value, b: &Value) -> f64 {
150    let num_a = to_f64(a);
151    let num_b = to_f64(b);
152    num_a - num_b
153}
154
155/// Create option object from label and value
156#[inline]
157pub fn create_option(label: &Value, value: &Value) -> Value {
158    serde_json::json!({"label": label, "value": value})
159}
160
161/// Generate scalar hash key for Value (used in In operations)
162#[inline]
163pub fn scalar_hash_key(value: &Value) -> Option<String> {
164    match value {
165        Value::Null => Some(String::from("null")),
166        Value::Bool(b) => Some(b.to_string()),
167        Value::Number(n) => Some(n.to_string()),
168        Value::String(s) => Some(s.clone()),
169        _ => None,
170    }
171}
172
173/// JavaScript-like loose equality
174pub fn loose_equal(a: &Value, b: &Value) -> bool {
175    // JavaScript-like type coercion for loose equality (==)
176    match (a, b) {
177        // Same type comparisons
178        (Value::Null, Value::Null) => true,
179        (Value::Bool(a), Value::Bool(b)) => a == b,
180        (Value::Number(a), Value::Number(b)) => {
181            let a_f64 = a.as_f64().unwrap_or(0.0);
182            let b_f64 = b.as_f64().unwrap_or(0.0);
183            a_f64 == b_f64
184        }
185        (Value::String(a), Value::String(b)) => a == b,
186
187        // Number and String: convert string to number
188        (Value::Number(n), Value::String(s)) | (Value::String(s), Value::Number(n)) => {
189            let n_val = n.as_f64().unwrap_or(0.0);
190            parse_string_to_f64(s).map(|parsed| n_val == parsed).unwrap_or(false)
191        }
192
193        // Boolean and Number: convert boolean to number (true=1, false=0)
194        (Value::Bool(b), Value::Number(n)) | (Value::Number(n), Value::Bool(b)) => {
195            let b_num = if *b { 1.0 } else { 0.0 };
196            b_num == n.as_f64().unwrap_or(0.0)
197        }
198
199        // Boolean and String: convert both to number
200        (Value::Bool(b), Value::String(s)) | (Value::String(s), Value::Bool(b)) => {
201            let b_num = if *b { 1.0 } else { 0.0 };
202            parse_string_to_f64(s).map(|parsed| b_num == parsed).unwrap_or(false)
203        }
204
205        // Null comparisons: null only equals null (and undefined, but we don't have that)
206        (Value::Null, _) | (_, Value::Null) => false,
207
208        // Default: strict equality
209        _ => a == b,
210    }
211}