1use std::cmp::Ordering;
9
10use crate::numeric_cmp::{Numeric, cmp_numeric, numeric_eq, parse_numeric_str};
11
12pub fn json_to_f64(v: &serde_json::Value, coerce_bool: bool) -> Option<f64> {
19 match v {
20 serde_json::Value::Number(n) => n.as_f64(),
21 serde_json::Value::String(s) => s.parse::<f64>().ok(),
22 serde_json::Value::Bool(b) if coerce_bool => Some(if *b { 1.0 } else { 0.0 }),
23 _ => None,
24 }
25}
26
27fn number_reading(n: &serde_json::Number) -> Option<Numeric> {
29 if let Some(i) = n.as_i64() {
30 return Some(Numeric::Int(i128::from(i)));
31 }
32 if let Some(u) = n.as_u64() {
33 return Some(Numeric::Int(i128::from(u)));
34 }
35 n.as_f64().map(Numeric::Float)
36}
37
38fn numeric_reading(v: &serde_json::Value) -> Option<Numeric> {
42 match v {
43 serde_json::Value::Number(n) => number_reading(n),
44 serde_json::Value::Bool(b) => Some(Numeric::Int(i128::from(*b))),
45 serde_json::Value::String(s) => parse_numeric_str(s),
46 _ => None,
47 }
48}
49
50pub fn compare_json_numbers(a: &serde_json::Number, b: &serde_json::Number) -> Option<Ordering> {
54 Some(cmp_numeric(number_reading(a)?, number_reading(b)?))
55}
56
57pub fn numeric_order(a: &serde_json::Value, b: &serde_json::Value) -> Option<Ordering> {
61 let (na, nb) = (numeric_reading(a)?, numeric_reading(b)?);
62 Some(cmp_numeric(na, nb))
63}
64
65pub fn compare_json(a: &serde_json::Value, b: &serde_json::Value) -> Ordering {
71 if let Some(order) = numeric_order(a, b) {
72 return order;
73 }
74 let sa = json_to_display_string(a);
76 let sb = json_to_display_string(b);
77 sa.cmp(&sb)
78}
79
80pub fn compare_json_optional(
82 a: Option<&serde_json::Value>,
83 b: Option<&serde_json::Value>,
84) -> Ordering {
85 match (a, b) {
86 (None, None) => Ordering::Equal,
87 (None, Some(_)) => Ordering::Less,
88 (Some(_), None) => Ordering::Greater,
89 (Some(a), Some(b)) => compare_json(a, b),
90 }
91}
92
93pub fn coerced_eq(a: &serde_json::Value, b: &serde_json::Value) -> bool {
99 if a == b {
100 return true;
101 }
102 match (numeric_reading(a), numeric_reading(b)) {
103 (Some(na), Some(nb)) => numeric_eq(na, nb),
104 _ => false,
105 }
106}
107
108pub fn is_truthy(v: &serde_json::Value) -> bool {
116 match v {
117 serde_json::Value::Bool(b) => *b,
118 serde_json::Value::Null => false,
119 serde_json::Value::Number(n) => n.as_f64().unwrap_or(0.0) != 0.0,
120 serde_json::Value::String(s) => !s.is_empty(),
121 _ => true,
122 }
123}
124
125pub fn json_to_display_string(v: &serde_json::Value) -> String {
132 match v {
133 serde_json::Value::String(s) => s.clone(),
134 serde_json::Value::Null => String::new(),
135 serde_json::Value::Number(n) => n.to_string(),
136 serde_json::Value::Bool(b) => b.to_string(),
137 other => other.to_string(),
138 }
139}
140
141pub fn to_json_number(n: f64) -> serde_json::Value {
145 if n.fract() == 0.0 && n.abs() < i64::MAX as f64 {
146 serde_json::Value::Number(serde_json::Number::from(n as i64))
147 } else {
148 serde_json::Number::from_f64(n)
149 .map(serde_json::Value::Number)
150 .unwrap_or(serde_json::Value::Null)
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use serde_json::json;
158
159 #[test]
160 fn coerced_eq_mixed_types() {
161 assert!(coerced_eq(&json!(5), &json!("5")));
162 assert!(coerced_eq(&json!(3.15), &json!("3.15")));
163 assert!(!coerced_eq(&json!(5), &json!("6")));
164 }
165
166 #[test]
167 fn coerced_eq_bool_numeric() {
168 assert!(coerced_eq(&json!(true), &json!(1)));
169 assert!(coerced_eq(&json!(false), &json!(0)));
170 assert!(!coerced_eq(&json!(true), &json!(0)));
171 }
172
173 #[test]
174 fn compare_numeric_coercion() {
175 assert_eq!(
176 compare_json(&json!(5), &json!("4")),
177 std::cmp::Ordering::Greater
178 );
179 assert_eq!(
180 compare_json(&json!("10"), &json!(9)),
181 std::cmp::Ordering::Greater
182 );
183 }
184
185 #[test]
187 fn integers_past_two_pow_53_compare_exactly() {
188 let above = json!(9_007_199_254_740_993_i64);
189 let at = json!(9_007_199_254_740_992_i64);
190 assert!(!coerced_eq(&above, &at));
191 assert_eq!(compare_json(&above, &at), Ordering::Greater);
192 assert_eq!(compare_json(&at, &above), Ordering::Less);
193 assert!(coerced_eq(&above, &json!(9_007_199_254_740_993_u64)));
194 }
195
196 #[test]
197 fn nanosecond_timestamps_one_tick_apart() {
198 let t0 = json!(1_700_000_000_000_000_001_i64);
199 let t1 = json!(1_700_000_000_000_000_002_i64);
200 assert!(!coerced_eq(&t0, &t1));
201 assert_eq!(compare_json(&t0, &t1), Ordering::Less);
202 assert_eq!(compare_json(&t1, &t0), Ordering::Greater);
203 }
204
205 #[test]
206 fn u64_against_i64_compares_exactly() {
207 let u_max = json!(u64::MAX);
208 let i_max = json!(i64::MAX);
209 assert!(!coerced_eq(&u_max, &i_max));
210 assert_eq!(compare_json(&u_max, &i_max), Ordering::Greater);
211 assert_eq!(compare_json(&i_max, &u_max), Ordering::Less);
212 assert_eq!(compare_json(&json!(u64::MAX - 1), &u_max), Ordering::Less);
213 assert_eq!(compare_json(&json!(i64::MIN), &u_max), Ordering::Less);
214 assert_eq!(
216 compare_json(&json!(9_223_372_036_854_775_808_u64), &i_max),
217 Ordering::Greater
218 );
219 assert_eq!(
220 compare_json_numbers(
221 &serde_json::Number::from(u64::MAX),
222 &serde_json::Number::from(i64::MAX)
223 ),
224 Some(Ordering::Greater)
225 );
226 }
227
228 #[test]
229 fn integer_against_float_compares_without_rounding() {
230 let above = json!(9_007_199_254_740_993_i64);
231 let float = json!(9_007_199_254_740_992.0_f64);
232 assert!(!coerced_eq(&above, &float));
233 assert_eq!(compare_json(&above, &float), Ordering::Greater);
234 assert_eq!(compare_json(&float, &above), Ordering::Less);
235 assert!(coerced_eq(&json!(9_007_199_254_740_992_i64), &float));
236 assert_eq!(compare_json(&json!(2), &json!(2.5)), Ordering::Less);
237 assert_eq!(compare_json(&json!(-2), &json!(-2.5)), Ordering::Greater);
238 assert!(coerced_eq(&json!(3), &json!(3.0)));
239 assert_eq!(
241 compare_json(&json!(u64::MAX), &json!(18_446_744_073_709_551_616.0_f64)),
242 Ordering::Less
243 );
244 assert!(!coerced_eq(
245 &json!(u64::MAX),
246 &json!(18_446_744_073_709_551_616.0_f64)
247 ));
248 assert_eq!(
249 compare_json(&json!(i64::MIN), &json!(-9_223_372_036_854_775_808.0_f64)),
250 Ordering::Equal
251 );
252 }
253
254 #[test]
255 fn integer_strings_compare_exactly_against_integers() {
256 let text = json!("9007199254740993");
257 assert!(!coerced_eq(&text, &json!(9_007_199_254_740_992_i64)));
258 assert!(coerced_eq(&text, &json!(9_007_199_254_740_993_i64)));
259 assert_eq!(
260 compare_json(&text, &json!(9_007_199_254_740_992_i64)),
261 Ordering::Greater
262 );
263 assert!(coerced_eq(&json!("18446744073709551615"), &json!(u64::MAX)));
264 assert!(coerced_eq(&json!("5.0"), &json!(5)));
265 }
266
267 #[test]
268 fn small_numbers_keep_their_order() {
269 assert_eq!(compare_json(&json!(1), &json!(2)), Ordering::Less);
270 assert_eq!(compare_json(&json!(1.5), &json!(1.25)), Ordering::Greater);
271 assert_eq!(compare_json(&json!(-3), &json!(-3)), Ordering::Equal);
272 assert_eq!(compare_json(&json!(true), &json!(0)), Ordering::Greater);
273 assert_eq!(compare_json(&json!("abc"), &json!("abd")), Ordering::Less);
274 assert!(!coerced_eq(&json!(0.1 + 0.2), &json!(0.3)));
276 assert!(coerced_eq(&json!(0.5), &json!(0.5)));
277 assert!(!coerced_eq(&json!("abc"), &json!(1)));
278 }
279
280 #[test]
283 fn nan_text_sorts_above_every_number() {
284 assert_eq!(compare_json(&json!("NaN"), &json!(1)), Ordering::Greater);
285 assert_eq!(compare_json(&json!(1e308), &json!("NaN")), Ordering::Less);
286 assert_eq!(compare_json(&json!("NaN"), &json!("NaN")), Ordering::Equal);
287 assert_eq!(
288 compare_json(&json!("NaN"), &json!("inf")),
289 Ordering::Greater
290 );
291 assert!(coerced_eq(&json!("NaN"), &json!("nan")));
292 let mut values = [
293 json!("NaN"),
294 json!(2),
295 json!("-inf"),
296 json!("NaN"),
297 json!(1.5),
298 ];
299 values.sort_by(compare_json);
300 assert_eq!(values[0], json!("-inf"));
301 assert_eq!(values[1], json!(1.5));
302 assert_eq!(values[2], json!(2));
303 }
304
305 #[test]
307 fn fractional_decimal_text_compares_exactly() {
308 let low = json!("12345678901234567.01");
309 let high = json!("12345678901234567.02");
310 assert_eq!(compare_json(&low, &high), Ordering::Less);
311 assert_eq!(compare_json(&high, &low), Ordering::Greater);
312 assert!(!coerced_eq(&low, &high));
313 assert_eq!(
314 compare_json(
315 &json!("9007199254740993.5"),
316 &json!(9_007_199_254_740_993_i64)
317 ),
318 Ordering::Greater
319 );
320 assert!(coerced_eq(&json!("2.50"), &json!("2.5")));
321 }
322
323 #[test]
324 fn truthiness() {
325 assert!(is_truthy(&json!(true)));
326 assert!(!is_truthy(&json!(false)));
327 assert!(!is_truthy(&json!(null)));
328 assert!(is_truthy(&json!(1)));
329 assert!(!is_truthy(&json!(0)));
330 assert!(is_truthy(&json!("hello")));
331 assert!(!is_truthy(&json!("")));
332 }
333
334 #[test]
335 fn to_json_number_nan() {
336 assert_eq!(to_json_number(f64::NAN), serde_json::Value::Null);
337 }
338
339 #[test]
340 fn to_json_number_integer() {
341 assert_eq!(to_json_number(42.0), json!(42));
342 }
343
344 #[test]
345 fn to_json_number_float() {
346 assert_eq!(to_json_number(3.15), json!(3.15));
347 }
348}