use std::cmp::Ordering;
pub fn json_to_f64(v: &serde_json::Value, coerce_bool: bool) -> Option<f64> {
match v {
serde_json::Value::Number(n) => n.as_f64(),
serde_json::Value::String(s) => s.parse::<f64>().ok(),
serde_json::Value::Bool(b) if coerce_bool => Some(if *b { 1.0 } else { 0.0 }),
_ => None,
}
}
pub fn compare_json(a: &serde_json::Value, b: &serde_json::Value) -> Ordering {
if let (Some(na), Some(nb)) = (json_to_f64(a, true), json_to_f64(b, true)) {
return na.partial_cmp(&nb).unwrap_or(Ordering::Equal);
}
let sa = json_to_display_string(a);
let sb = json_to_display_string(b);
sa.cmp(&sb)
}
pub fn compare_json_optional(
a: Option<&serde_json::Value>,
b: Option<&serde_json::Value>,
) -> Ordering {
match (a, b) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some(a), Some(b)) => compare_json(a, b),
}
}
pub fn coerced_eq(a: &serde_json::Value, b: &serde_json::Value) -> bool {
if a == b {
return true;
}
if let (Some(af), Some(bf)) = (json_to_f64(a, true), json_to_f64(b, true)) {
return (af - bf).abs() < f64::EPSILON;
}
false
}
pub fn is_truthy(v: &serde_json::Value) -> bool {
match v {
serde_json::Value::Bool(b) => *b,
serde_json::Value::Null => false,
serde_json::Value::Number(n) => n.as_f64().unwrap_or(0.0) != 0.0,
serde_json::Value::String(s) => !s.is_empty(),
_ => true,
}
}
pub fn json_to_display_string(v: &serde_json::Value) -> String {
match v {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Null => String::new(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
other => other.to_string(),
}
}
pub fn to_json_number(n: f64) -> serde_json::Value {
if n.fract() == 0.0 && n.abs() < i64::MAX as f64 {
serde_json::Value::Number(serde_json::Number::from(n as i64))
} else {
serde_json::Number::from_f64(n)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn coerced_eq_mixed_types() {
assert!(coerced_eq(&json!(5), &json!("5")));
assert!(coerced_eq(&json!(3.15), &json!("3.15")));
assert!(!coerced_eq(&json!(5), &json!("6")));
}
#[test]
fn coerced_eq_bool_numeric() {
assert!(coerced_eq(&json!(true), &json!(1)));
assert!(coerced_eq(&json!(false), &json!(0)));
assert!(!coerced_eq(&json!(true), &json!(0)));
}
#[test]
fn compare_numeric_coercion() {
assert_eq!(compare_json(&json!(5), &json!("4")), Ordering::Greater);
assert_eq!(compare_json(&json!("10"), &json!(9)), Ordering::Greater);
}
#[test]
fn truthiness() {
assert!(is_truthy(&json!(true)));
assert!(!is_truthy(&json!(false)));
assert!(!is_truthy(&json!(null)));
assert!(is_truthy(&json!(1)));
assert!(!is_truthy(&json!(0)));
assert!(is_truthy(&json!("hello")));
assert!(!is_truthy(&json!("")));
}
#[test]
fn to_json_number_nan() {
assert_eq!(to_json_number(f64::NAN), serde_json::Value::Null);
}
#[test]
fn to_json_number_integer() {
assert_eq!(to_json_number(42.0), json!(42));
}
#[test]
fn to_json_number_float() {
assert_eq!(to_json_number(3.15), json!(3.15));
}
}