1use std::cmp::Ordering;
9
10pub fn json_to_f64(v: &serde_json::Value, coerce_bool: bool) -> Option<f64> {
17 match v {
18 serde_json::Value::Number(n) => n.as_f64(),
19 serde_json::Value::String(s) => s.parse::<f64>().ok(),
20 serde_json::Value::Bool(b) if coerce_bool => Some(if *b { 1.0 } else { 0.0 }),
21 _ => None,
22 }
23}
24
25pub fn compare_json(a: &serde_json::Value, b: &serde_json::Value) -> Ordering {
30 if let (Some(na), Some(nb)) = (json_to_f64(a, true), json_to_f64(b, true)) {
32 return na.partial_cmp(&nb).unwrap_or(Ordering::Equal);
33 }
34 let sa = json_to_display_string(a);
36 let sb = json_to_display_string(b);
37 sa.cmp(&sb)
38}
39
40pub fn compare_json_optional(
42 a: Option<&serde_json::Value>,
43 b: Option<&serde_json::Value>,
44) -> Ordering {
45 match (a, b) {
46 (None, None) => Ordering::Equal,
47 (None, Some(_)) => Ordering::Less,
48 (Some(_), None) => Ordering::Greater,
49 (Some(a), Some(b)) => compare_json(a, b),
50 }
51}
52
53pub fn coerced_eq(a: &serde_json::Value, b: &serde_json::Value) -> bool {
58 if a == b {
59 return true;
60 }
61 if let (Some(af), Some(bf)) = (json_to_f64(a, true), json_to_f64(b, true)) {
62 return (af - bf).abs() < f64::EPSILON;
63 }
64 false
65}
66
67pub fn is_truthy(v: &serde_json::Value) -> bool {
75 match v {
76 serde_json::Value::Bool(b) => *b,
77 serde_json::Value::Null => false,
78 serde_json::Value::Number(n) => n.as_f64().unwrap_or(0.0) != 0.0,
79 serde_json::Value::String(s) => !s.is_empty(),
80 _ => true,
81 }
82}
83
84pub fn json_to_display_string(v: &serde_json::Value) -> String {
91 match v {
92 serde_json::Value::String(s) => s.clone(),
93 serde_json::Value::Null => String::new(),
94 serde_json::Value::Number(n) => n.to_string(),
95 serde_json::Value::Bool(b) => b.to_string(),
96 other => other.to_string(),
97 }
98}
99
100pub fn to_json_number(n: f64) -> serde_json::Value {
104 if n.fract() == 0.0 && n.abs() < i64::MAX as f64 {
105 serde_json::Value::Number(serde_json::Number::from(n as i64))
106 } else {
107 serde_json::Number::from_f64(n)
108 .map(serde_json::Value::Number)
109 .unwrap_or(serde_json::Value::Null)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use serde_json::json;
117
118 #[test]
119 fn coerced_eq_mixed_types() {
120 assert!(coerced_eq(&json!(5), &json!("5")));
121 assert!(coerced_eq(&json!(3.15), &json!("3.15")));
122 assert!(!coerced_eq(&json!(5), &json!("6")));
123 }
124
125 #[test]
126 fn coerced_eq_bool_numeric() {
127 assert!(coerced_eq(&json!(true), &json!(1)));
128 assert!(coerced_eq(&json!(false), &json!(0)));
129 assert!(!coerced_eq(&json!(true), &json!(0)));
130 }
131
132 #[test]
133 fn compare_numeric_coercion() {
134 assert_eq!(
135 compare_json(&json!(5), &json!("4")),
136 std::cmp::Ordering::Greater
137 );
138 assert_eq!(
139 compare_json(&json!("10"), &json!(9)),
140 std::cmp::Ordering::Greater
141 );
142 }
143
144 #[test]
145 fn truthiness() {
146 assert!(is_truthy(&json!(true)));
147 assert!(!is_truthy(&json!(false)));
148 assert!(!is_truthy(&json!(null)));
149 assert!(is_truthy(&json!(1)));
150 assert!(!is_truthy(&json!(0)));
151 assert!(is_truthy(&json!("hello")));
152 assert!(!is_truthy(&json!("")));
153 }
154
155 #[test]
156 fn to_json_number_nan() {
157 assert_eq!(to_json_number(f64::NAN), serde_json::Value::Null);
158 }
159
160 #[test]
161 fn to_json_number_integer() {
162 assert_eq!(to_json_number(42.0), json!(42));
163 }
164
165 #[test]
166 fn to_json_number_float() {
167 assert_eq!(to_json_number(3.15), json!(3.15));
168 }
169}