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