use serde_json::{Number, Value};
pub fn is_truthy(value: &Value) -> bool {
match value {
Value::Array(arr) => !arr.is_empty(),
Value::Bool(b) => *b,
Value::Null => false,
Value::Number(num) => num.as_f64().unwrap() != 0f64,
Value::Object(_) => true,
Value::String(s) => s != "",
}
}
pub fn is_strict_equal(a: &Value, b: &Value) -> bool {
use Value::*;
match (a, b) {
(Array(_), Array(_)) => false,
(Bool(a), Bool(b)) => a == b,
(Null, Null) => true,
(Number(a), Number(b)) => equal_numbers(a, b),
(Object(_), Object(_)) => false,
(String(a), String(b)) => a == b,
_ => false,
}
}
#[allow(clippy::float_cmp)]
pub fn is_abstract_equal(a: &Value, b: &Value) -> bool {
use Value::*;
match (a, b) {
(Array(_), Array(_))
| (Bool(_), Bool(_))
| (Null, Null)
| (Number(_), Number(_))
| (Object(_), Object(_))
| (String(_), String(_)) => is_strict_equal(a, b),
(Null, _) | (_, Null) => false,
(Number(a), String(_)) => coerce_to_f64(b)
.map(|b| a.as_f64().unwrap() == b)
.unwrap_or(false),
(String(_), Number(b)) => coerce_to_f64(a)
.map(|a| a == b.as_f64().unwrap())
.unwrap_or(false),
(Bool(_), _) => coerce_to_f64(a)
.map(|a| is_abstract_equal(&Value::Number(serde_json::Number::from_f64(a).unwrap()), b))
.unwrap_or(false),
(_, Bool(_)) => coerce_to_f64(b)
.map(|b| is_abstract_equal(a, &Value::Number(serde_json::Number::from_f64(b).unwrap())))
.unwrap_or(false),
(Object(_), _) | (_, Object(_)) => false,
(String(a), Array(b)) | (Array(b), String(a)) => a == &arr_to_primitive_str(b),
(Number(_), Array(b)) => is_abstract_equal(a, &Value::String(arr_to_primitive_str(b))),
(Array(a), Number(_)) => is_abstract_equal(&Value::String(arr_to_primitive_str(a)), b),
}
}
pub fn less_than(a: &Value, b: &Value) -> bool {
use Value::*;
match (a, b) {
(Null, Null) => false,
(Bool(false), Bool(true)) => true,
(Bool(_), Bool(_)) => false,
(Object(_), _) | (_, Object(_)) => false,
(String(a), String(b)) => a < b,
(Array(_), Array(_)) | (Array(_), String(_)) | (String(_), Array(_)) => {
coerce_to_str(a) < coerce_to_str(b)
}
(Null, _) | (_, Null) | (Number(_), _) | (_, Number(_)) | (Bool(_), _) | (_, Bool(_)) => {
match (coerce_to_f64(a), coerce_to_f64(b)) {
(Some(a), Some(b)) => a < b,
_ => false,
}
}
}
}
pub fn less_equal_than(a: &Value, b: &Value) -> bool {
less_than(a, b) || is_abstract_equal(a, b)
}
pub fn greater_than(a: &Value, b: &Value) -> bool {
!less_equal_than(a, b)
}
pub fn greater_equal_than(a: &Value, b: &Value) -> bool {
!less_than(a, b)
}
pub fn coerce_to_str(val: &Value) -> String {
match val {
Value::Array(arr) => arr_to_primitive_str(arr),
Value::Bool(b) => b.to_string(),
Value::Null => String::from("null"),
Value::Number(num) => num.to_string(),
Value::Object(_) => String::from("[object Object]"),
Value::String(s) => s.to_string(),
}
}
pub fn coerce_to_f64(val: &Value) -> Option<f64> {
match val {
Value::Array(arr) => match &arr[..] {
[] => Some(0f64),
[el] => match el {
Value::Array(_) | Value::Null | Value::Number(_) | Value::String(_) => {
coerce_to_f64(el)
}
_ => None,
},
_ => None,
},
Value::Bool(true) => Some(1f64),
Value::Bool(false) => Some(0f64),
Value::Null => Some(0f64),
Value::Number(num) => num.as_f64(),
Value::Object(_) => None,
Value::String(s) => {
let s = s.trim();
if s == "" {
Some(0f64)
} else {
s.parse::<f64>().ok()
}
}
}
}
pub fn parse_float(val: &Value) -> Option<f64> {
match val {
Value::Number(num) => Some(num.as_f64().unwrap()),
Value::String(s) => {
let s = s.trim();
let mut end = 0;
let mut has_decimal_point = false;
for ch in s.chars() {
match ch {
'+' | '-' | '0'..='9' | 'e' | 'E' => end += 1,
'.' => {
if has_decimal_point {
break;
} else {
end += 1;
has_decimal_point = true;
}
}
_ => break,
}
}
let parsed = &s[0..end];
parsed.parse::<f64>().ok()
}
_ => None,
}
}
#[allow(clippy::float_cmp)]
fn equal_numbers(a: &Number, b: &Number) -> bool {
if a.is_u64() && b.is_u64() {
a.as_u64().unwrap() == b.as_u64().unwrap()
} else if a.is_i64() && b.is_i64() {
a.as_i64().unwrap() == b.as_i64().unwrap()
} else {
a.as_f64().unwrap() == b.as_f64().unwrap()
}
}
fn arr_to_primitive_str(arr: &[Value]) -> String {
arr.iter()
.map(|el| coerce_to_str(el))
.collect::<Vec<String>>()
.join(",")
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
mod strict_equal {
use super::*;
macro_rules! test_strict_equal {
($a:expr, $b:expr) => {
assert!(is_strict_equal(&json!($a), &json!($b)));
assert!(is_strict_equal(&json!($b), &json!($a)));
};
}
macro_rules! test_strict_not_equal {
($a:expr, $b:expr) => {
assert!(!is_strict_equal(&json!($a), &json!($b)));
assert!(!is_strict_equal(&json!($b), &json!($a)));
};
}
#[test]
fn same_type_string() {
test_strict_equal!("", "");
test_strict_not_equal!(" ", "");
test_strict_equal!("a", "a");
test_strict_not_equal!("a", "b");
}
#[test]
fn same_type_number() {
test_strict_equal!(0, 0);
test_strict_equal!(0, 0.0);
test_strict_equal!(-0, 0);
test_strict_not_equal!(-1, 1);
test_strict_not_equal!(1.1, 1);
}
#[test]
fn same_type_bool() {
test_strict_equal!(true, true);
test_strict_equal!(false, false);
test_strict_not_equal!(false, true);
}
#[test]
fn same_type_object() {
assert!(!is_strict_equal(&json!({}), &json!({}),));
assert!(!is_strict_equal(
&json!({"foo": "bar"}),
&json!({"foo": "bar"}),
));
assert!(!is_strict_equal(
&json!({"foo": "bar"}),
&json!({"foo": 1}),
));
}
#[test]
fn same_type_array() {
assert!(!is_strict_equal(&json!([]), &json!([])));
assert!(!is_strict_equal(&json!([1]), &json!([1])));
assert!(!is_strict_equal(&json!([1]), &json!([2])));
}
#[test]
fn different_type() {
test_strict_not_equal!("", 0);
test_strict_not_equal!("1", 1);
test_strict_not_equal!("true", true);
test_strict_not_equal!(1, true);
assert!(!is_strict_equal(&json!(null), &json!(false)));
}
}
mod abstract_equal {
use super::*;
macro_rules! test_abstract_equal {
($a:expr, $b:expr) => {
assert!(is_abstract_equal(&json!($a), &json!($b)));
assert!(is_abstract_equal(&json!($b), &json!($a)));
};
}
macro_rules! test_abstract_not_equal {
($a:expr, $b:expr) => {
assert!(!is_abstract_equal(&json!($a), &json!($b)),);
assert!(!is_abstract_equal(&json!($b), &json!($a)),);
};
}
#[test]
fn loose_equal_same_type() {
test_abstract_equal!(Value::Null, Value::Null);
test_abstract_equal!(true, true);
test_abstract_equal!(false, false);
test_abstract_equal!("foo", "foo");
test_abstract_equal!(0, 0);
test_abstract_equal!(0, -0);
test_abstract_equal!(0, 0.0);
test_abstract_equal!(0.2, 0.2);
}
#[test]
fn loose_equal_diff_type() {
test_abstract_equal!([1, 2], "1,2");
}
#[test]
fn loose_not_equal() {
test_abstract_not_equal!(0, &Value::Null);
}
#[test]
fn number_boolean() {
test_abstract_equal!(-0, false);
test_abstract_equal!(0, false);
test_abstract_equal!(1, true);
test_abstract_equal!(1.0, true);
test_abstract_not_equal!(-1, true);
test_abstract_not_equal!(0.1 + 0.2, false);
}
#[test]
fn number_string() {
test_abstract_equal!("", 0);
test_abstract_equal!("0", 0);
test_abstract_equal!("-0", 0);
test_abstract_equal!("+0", 0);
test_abstract_equal!("0.0", 0);
test_abstract_equal!("+0.0", 0);
test_abstract_equal!("-0.0", 0);
test_abstract_equal!("17", 17);
test_abstract_equal!("-17", -17);
test_abstract_equal!(" 1 ", 1);
test_abstract_equal!(" 1.3 ", 1.3);
}
#[test]
fn array_bool() {
test_abstract_equal!([1], true);
test_abstract_not_equal!([true], true);
}
#[test]
fn string_bool() {
test_abstract_equal!("", false);
test_abstract_equal!(" ", false);
test_abstract_equal!("0", false);
test_abstract_equal!(" 0 ", false);
test_abstract_equal!("1", true);
test_abstract_equal!(" 1 ", true);
}
#[test]
fn number_array() {
test_abstract_equal!([1], 1);
test_abstract_equal!([1.2], 1.2);
}
}
mod test_less_than {
use super::*;
macro_rules! less_than {
($a:expr, $b:expr, $result:expr) => {
assert_eq!(less_than(&json!($a), &json!($b)), $result);
};
}
#[test]
fn same_type() {
assert_eq!(less_than(&json!(1), &json!(2)), true);
assert_eq!(less_than(&json!(2), &json!(2)), false);
assert_eq!(less_than(&json!(3), &json!(2)), false);
assert_eq!(less_than(&json!("a"), &json!("b")), true);
assert_eq!(less_than(&json!("b"), &json!("b")), false);
assert_eq!(less_than(&json!("c"), &json!("b")), false);
assert_eq!(less_than(&json!(null), &json!(null)), false);
assert_eq!(less_than(&json!(false), &json!(true)), true);
assert_eq!(less_than(&json!(true), &json!(false)), false);
assert_eq!(less_than(&json!(true), &json!(true)), false);
assert_eq!(less_than(&json!(false), &json!(false)), false);
}
#[test]
fn number_string() {
assert_eq!(less_than(&json!(1), &json!("b")), false);
assert_eq!(less_than(&json!(1), &json!("1")), false);
assert_eq!(less_than(&json!(-1), &json!("")), true);
assert_eq!(less_than(&json!(1), &json!("12")), true);
assert_eq!(less_than(&json!("b"), &json!(1)), false);
assert_eq!(less_than(&json!("1"), &json!(1)), false);
assert_eq!(less_than(&json!(""), &json!(-1)), false);
assert_eq!(less_than(&json!("12"), &json!(1)), false);
}
#[test]
fn array_number() {
assert_eq!(less_than(&json!([1]), &json!(12)), true);
assert_eq!(less_than(&json!([2]), &json!(12)), true);
assert_eq!(less_than(&json!([[2]]), &json!(12)), true);
assert_eq!(less_than(&json!([[2], 3]), &json!(12)), false);
assert_eq!(less_than(&json!(1), &json!([12])), true);
assert_eq!(less_than(&json!(2), &json!([12])), true);
assert_eq!(less_than(&json!(2), &json!([[12]])), true);
assert_eq!(less_than(&json!(2), &json!([10, [12]])), false);
}
#[test]
fn multi_elem_arrays() {
assert_eq!(less_than(&json!([1, 2]), &json!([3, 4])), true);
assert_eq!(less_than(&json!([3, 4]), &json!([1, 2])), false);
assert_eq!(less_than(&json!([1, 2, 2]), &json!([2, 2])), true);
}
#[test]
fn bool_number() {
assert_eq!(less_than(&json!(false), &json!(1)), true);
assert_eq!(less_than(&json!(true), &json!(1)), false);
assert_eq!(less_than(&json!(true), &json!(2)), true);
assert_eq!(less_than(&json!(-1), &json!(false)), true);
assert_eq!(less_than(&json!(1), &json!(true)), false);
assert_eq!(less_than(&json!(0), &json!(true)), true);
}
#[test]
fn bool_string() {
assert_eq!(less_than(&json!(false), &json!("1")), true);
assert_eq!(less_than(&json!(true), &json!("1")), false);
assert_eq!(less_than(&json!(true), &json!("2")), true);
assert_eq!(less_than(&json!(true), &json!("foo")), false);
assert_eq!(less_than(&json!("-1"), &json!(false)), true);
assert_eq!(less_than(&json!("1"), &json!(true)), false);
assert_eq!(less_than(&json!("0"), &json!(true)), true);
assert_eq!(less_than(&json!("foo"), &json!(true)), false);
}
#[test]
fn bool_array() {
less_than!(false, [true], false);
less_than!(false, [false], false);
less_than!(false, [0], false);
less_than!(false, [1], true);
less_than!(false, [1, 2], false);
less_than!(true, [true], false);
less_than!(true, [false], false);
less_than!(true, [0], false);
less_than!(true, [1], false);
less_than!(true, [2], true);
less_than!(true, [2, 3], false);
}
#[test]
fn string_array() {
assert_eq!(less_than(&json!([1]), &json!("12")), true);
assert_eq!(less_than(&json!([2]), &json!("12")), false);
}
#[test]
fn with_null() {
macro_rules! null_less_than {
($a:expr, $b:expr) => {
assert_eq!(less_than(&json!(null), &json!($a)), $b);
};
}
macro_rules! is_less_than_null {
($a:expr, $b:expr) => {
assert_eq!(less_than(&json!($a), &json!(null)), $b);
};
}
null_less_than!(1, true);
null_less_than!("5", true);
null_less_than!(true, true);
null_less_than!({}, false);
null_less_than!([-5], false);
null_less_than!(["-5"], false);
null_less_than!([5], true);
null_less_than!(["5"], true);
is_less_than_null!(-1, true);
is_less_than_null!(1, false);
is_less_than_null!("-1", true);
is_less_than_null!("1", false);
is_less_than_null!({}, false);
is_less_than_null!([-5], true);
is_less_than_null!(["-5"], true);
is_less_than_null!([5], false);
is_less_than_null!(["5"], false);
}
}
#[allow(clippy::approx_constant)]
mod parse_float {
use super::*;
#[test]
fn success() {
let result = Some(3.14);
assert_eq!(parse_float(&json!(3.14)), result);
assert_eq!(parse_float(&json!("3.14")), result);
assert_eq!(parse_float(&json!("3.14.5")), result);
assert_eq!(parse_float(&json!(" 3.14 ")), result);
assert_eq!(parse_float(&json!("314e-2")), result);
assert_eq!(parse_float(&json!("0.0314E+2")), result);
assert_eq!(parse_float(&json!("0.0314e+2")), result);
assert_eq!(parse_float(&json!("3.14some non-digit characters")), result);
}
#[test]
fn nan() {
assert_eq!(parse_float(&json!("FF2")), None);
}
#[test]
fn sign() {
assert_eq!(parse_float(&json!("+3.14")), Some(3.14));
assert_eq!(parse_float(&json!("-3.14")), Some(-3.14));
}
}
#[test]
fn test_less_equal_than() {
assert_eq!(less_equal_than(&json!(1), &json!(1)), true);
assert_eq!(less_equal_than(&json!([1]), &json!("1")), true);
assert_eq!(less_equal_than(&json!([1]), &json!("12")), true);
assert_eq!(less_equal_than(&json!(2), &json!(1)), false);
assert_eq!(less_equal_than(&json!([2]), &json!("12")), false);
}
#[test]
fn test_greater_than() {
assert_eq!(greater_than(&json!(2), &json!(1)), true);
assert_eq!(greater_than(&json!(2), &json!(2)), false);
assert_eq!(greater_than(&json!(2), &json!(3)), false);
}
#[test]
fn test_greater_equal_than() {
assert_eq!(greater_equal_than(&json!(2), &json!(1)), true);
assert_eq!(greater_equal_than(&json!(2), &json!(2)), true);
assert_eq!(greater_equal_than(&json!(2), &json!(3)), false);
}
#[test]
fn truthy_values() {
assert_eq!(is_truthy(&json!(0)), false);
assert_eq!(is_truthy(&json!(-1)), true);
assert_eq!(is_truthy(&json!(1)), true);
assert_eq!(is_truthy(&json!([])), false);
assert_eq!(is_truthy(&json!([1, 2])), true);
assert_eq!(is_truthy(&json!("")), false);
assert_eq!(is_truthy(&json!("anything")), true);
assert_eq!(is_truthy(&json!("0")), true);
assert_eq!(is_truthy(&json!(["0"])), true);
assert_eq!(is_truthy(&json!(null)), false);
assert_eq!(is_truthy(&json!({})), true);
assert_eq!(is_truthy(&json!(true)), true);
assert_eq!(is_truthy(&json!(false)), false);
}
#[test]
fn value_as_string_coercion() {
assert_eq!(coerce_to_str(&json!(true)), "true");
assert_eq!(coerce_to_str(&json!(false)), "false");
assert_eq!(coerce_to_str(&json!([false])), "false");
assert_eq!(coerce_to_str(&json!([true])), "true");
assert_eq!(coerce_to_str(&json!(null)), "null");
assert_eq!(coerce_to_str(&json!({})), "[object Object]");
assert_eq!(coerce_to_str(&json!([1, 2])), "1,2");
assert_eq!(coerce_to_str(&json!([[1, 2], [3, 4]])), "1,2,3,4");
assert_eq!(
coerce_to_str(&json!([[1, 2], [[true, 4]], 5])),
"1,2,true,4,5"
);
}
#[test]
fn value_as_f64_coercion() {
assert_eq!(coerce_to_f64(&json!([[[5]]])), Some(5f64));
assert_eq!(coerce_to_f64(&json!([[[5], 6]])), None);
assert_eq!(coerce_to_f64(&json!([[[1, 2]]])), None);
assert_eq!(coerce_to_f64(&json!(null)), Some(0f64));
assert_eq!(coerce_to_f64(&json!(true)), Some(1f64));
assert_eq!(coerce_to_f64(&json!([true])), None);
assert_eq!(coerce_to_f64(&json!(false)), Some(0f64));
assert_eq!(coerce_to_f64(&json!([false])), None);
assert_eq!(coerce_to_f64(&json!("1")), Some(1f64));
assert_eq!(coerce_to_f64(&json!("1.1")), Some(1.1));
assert_eq!(coerce_to_f64(&json!("1.8")), Some(1.8));
assert_eq!(coerce_to_f64(&json!(["1"])), Some(1f64));
assert_eq!(coerce_to_f64(&json!(null)), Some(0f64));
assert_eq!(coerce_to_f64(&json!([null])), Some(0f64));
}
}