use std::cmp::Ordering;
use crate::numeric_cmp::{Numeric, cmp_numeric, numeric_eq, parse_numeric_str};
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,
}
}
fn number_reading(n: &serde_json::Number) -> Option<Numeric> {
if let Some(i) = n.as_i64() {
return Some(Numeric::Int(i128::from(i)));
}
if let Some(u) = n.as_u64() {
return Some(Numeric::Int(i128::from(u)));
}
n.as_f64().map(Numeric::Float)
}
fn numeric_reading(v: &serde_json::Value) -> Option<Numeric> {
match v {
serde_json::Value::Number(n) => number_reading(n),
serde_json::Value::Bool(b) => Some(Numeric::Int(i128::from(*b))),
serde_json::Value::String(s) => parse_numeric_str(s),
_ => None,
}
}
pub fn compare_json_numbers(a: &serde_json::Number, b: &serde_json::Number) -> Option<Ordering> {
Some(cmp_numeric(number_reading(a)?, number_reading(b)?))
}
pub fn numeric_order(a: &serde_json::Value, b: &serde_json::Value) -> Option<Ordering> {
let (na, nb) = (numeric_reading(a)?, numeric_reading(b)?);
Some(cmp_numeric(na, nb))
}
pub fn compare_json(a: &serde_json::Value, b: &serde_json::Value) -> Ordering {
if let Some(order) = numeric_order(a, b) {
return order;
}
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;
}
match (numeric_reading(a), numeric_reading(b)) {
(Some(na), Some(nb)) => numeric_eq(na, nb),
_ => 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")),
std::cmp::Ordering::Greater
);
assert_eq!(
compare_json(&json!("10"), &json!(9)),
std::cmp::Ordering::Greater
);
}
#[test]
fn integers_past_two_pow_53_compare_exactly() {
let above = json!(9_007_199_254_740_993_i64);
let at = json!(9_007_199_254_740_992_i64);
assert!(!coerced_eq(&above, &at));
assert_eq!(compare_json(&above, &at), Ordering::Greater);
assert_eq!(compare_json(&at, &above), Ordering::Less);
assert!(coerced_eq(&above, &json!(9_007_199_254_740_993_u64)));
}
#[test]
fn nanosecond_timestamps_one_tick_apart() {
let t0 = json!(1_700_000_000_000_000_001_i64);
let t1 = json!(1_700_000_000_000_000_002_i64);
assert!(!coerced_eq(&t0, &t1));
assert_eq!(compare_json(&t0, &t1), Ordering::Less);
assert_eq!(compare_json(&t1, &t0), Ordering::Greater);
}
#[test]
fn u64_against_i64_compares_exactly() {
let u_max = json!(u64::MAX);
let i_max = json!(i64::MAX);
assert!(!coerced_eq(&u_max, &i_max));
assert_eq!(compare_json(&u_max, &i_max), Ordering::Greater);
assert_eq!(compare_json(&i_max, &u_max), Ordering::Less);
assert_eq!(compare_json(&json!(u64::MAX - 1), &u_max), Ordering::Less);
assert_eq!(compare_json(&json!(i64::MIN), &u_max), Ordering::Less);
assert_eq!(
compare_json(&json!(9_223_372_036_854_775_808_u64), &i_max),
Ordering::Greater
);
assert_eq!(
compare_json_numbers(
&serde_json::Number::from(u64::MAX),
&serde_json::Number::from(i64::MAX)
),
Some(Ordering::Greater)
);
}
#[test]
fn integer_against_float_compares_without_rounding() {
let above = json!(9_007_199_254_740_993_i64);
let float = json!(9_007_199_254_740_992.0_f64);
assert!(!coerced_eq(&above, &float));
assert_eq!(compare_json(&above, &float), Ordering::Greater);
assert_eq!(compare_json(&float, &above), Ordering::Less);
assert!(coerced_eq(&json!(9_007_199_254_740_992_i64), &float));
assert_eq!(compare_json(&json!(2), &json!(2.5)), Ordering::Less);
assert_eq!(compare_json(&json!(-2), &json!(-2.5)), Ordering::Greater);
assert!(coerced_eq(&json!(3), &json!(3.0)));
assert_eq!(
compare_json(&json!(u64::MAX), &json!(18_446_744_073_709_551_616.0_f64)),
Ordering::Less
);
assert!(!coerced_eq(
&json!(u64::MAX),
&json!(18_446_744_073_709_551_616.0_f64)
));
assert_eq!(
compare_json(&json!(i64::MIN), &json!(-9_223_372_036_854_775_808.0_f64)),
Ordering::Equal
);
}
#[test]
fn integer_strings_compare_exactly_against_integers() {
let text = json!("9007199254740993");
assert!(!coerced_eq(&text, &json!(9_007_199_254_740_992_i64)));
assert!(coerced_eq(&text, &json!(9_007_199_254_740_993_i64)));
assert_eq!(
compare_json(&text, &json!(9_007_199_254_740_992_i64)),
Ordering::Greater
);
assert!(coerced_eq(&json!("18446744073709551615"), &json!(u64::MAX)));
assert!(coerced_eq(&json!("5.0"), &json!(5)));
}
#[test]
fn small_numbers_keep_their_order() {
assert_eq!(compare_json(&json!(1), &json!(2)), Ordering::Less);
assert_eq!(compare_json(&json!(1.5), &json!(1.25)), Ordering::Greater);
assert_eq!(compare_json(&json!(-3), &json!(-3)), Ordering::Equal);
assert_eq!(compare_json(&json!(true), &json!(0)), Ordering::Greater);
assert_eq!(compare_json(&json!("abc"), &json!("abd")), Ordering::Less);
assert!(!coerced_eq(&json!(0.1 + 0.2), &json!(0.3)));
assert!(coerced_eq(&json!(0.5), &json!(0.5)));
assert!(!coerced_eq(&json!("abc"), &json!(1)));
}
#[test]
fn nan_text_sorts_above_every_number() {
assert_eq!(compare_json(&json!("NaN"), &json!(1)), Ordering::Greater);
assert_eq!(compare_json(&json!(1e308), &json!("NaN")), Ordering::Less);
assert_eq!(compare_json(&json!("NaN"), &json!("NaN")), Ordering::Equal);
assert_eq!(
compare_json(&json!("NaN"), &json!("inf")),
Ordering::Greater
);
assert!(coerced_eq(&json!("NaN"), &json!("nan")));
let mut values = [
json!("NaN"),
json!(2),
json!("-inf"),
json!("NaN"),
json!(1.5),
];
values.sort_by(compare_json);
assert_eq!(values[0], json!("-inf"));
assert_eq!(values[1], json!(1.5));
assert_eq!(values[2], json!(2));
}
#[test]
fn fractional_decimal_text_compares_exactly() {
let low = json!("12345678901234567.01");
let high = json!("12345678901234567.02");
assert_eq!(compare_json(&low, &high), Ordering::Less);
assert_eq!(compare_json(&high, &low), Ordering::Greater);
assert!(!coerced_eq(&low, &high));
assert_eq!(
compare_json(
&json!("9007199254740993.5"),
&json!(9_007_199_254_740_993_i64)
),
Ordering::Greater
);
assert!(coerced_eq(&json!("2.50"), &json!("2.5")));
}
#[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));
}
}