use crate::constants::NAN_ERROR;
use serde_json::Value;
pub fn access_path_ref<'a>(value: &'a Value, path: &str) -> Option<&'a Value> {
if path.is_empty() {
return Some(value);
}
if !path.contains('.') {
if let Value::Object(obj) = value
&& let Some(val) = obj.get(path)
{
return Some(val);
}
if let Ok(index) = path.parse::<usize>()
&& let Value::Array(arr) = value
{
return arr.get(index);
}
return None;
}
let mut current = value;
for part in path.split('.') {
match current {
Value::Object(obj) => {
if let Some(val) = obj.get(part) {
current = val;
} else {
return None;
}
}
Value::Array(arr) => {
if let Ok(index) = part.parse::<usize>() {
if let Some(val) = arr.get(index) {
current = val;
} else {
return None;
}
} else {
return None;
}
}
_ => return None,
}
}
Some(current)
}
pub fn access_path(value: &Value, path: &str) -> Option<Value> {
access_path_ref(value, path).cloned()
}
pub fn coerce_to_number(value: &Value, engine: &crate::DataLogic) -> Option<f64> {
match value {
Value::Number(n) => n.as_f64(),
Value::String(s) => {
if s.is_empty() && engine.config().numeric_coercion.empty_string_to_zero {
Some(0.0)
} else {
s.parse().ok()
}
}
Value::Bool(b) if engine.config().numeric_coercion.bool_to_number => {
Some(if *b { 1.0 } else { 0.0 })
}
Value::Null if engine.config().numeric_coercion.null_to_zero => Some(0.0),
_ => None,
}
}
pub fn strict_equals(left: &Value, right: &Value) -> bool {
left == right
}
enum LooseEqualsResult {
Equal,
NotEqual,
Incompatible,
}
fn loose_equals_core(left: &Value, right: &Value) -> LooseEqualsResult {
use LooseEqualsResult::*;
match (left, right) {
(Value::Null, Value::Null) => Equal,
(Value::Bool(a), Value::Bool(b)) => {
if a == b {
Equal
} else {
NotEqual
}
}
(Value::String(a), Value::String(b)) => {
if a == b {
Equal
} else {
NotEqual
}
}
(Value::Number(a), Value::Number(b)) => {
let a_f = a.as_f64().unwrap_or(f64::NAN);
let b_f = b.as_f64().unwrap_or(f64::NAN);
if !a_f.is_nan() && !b_f.is_nan() && a_f == b_f {
Equal
} else {
NotEqual
}
}
(Value::Number(n), Value::String(s)) | (Value::String(s), Value::Number(n)) => {
match (n.as_f64(), s.parse::<f64>().ok()) {
(Some(n_f), Some(s_f)) if n_f == s_f => Equal,
(Some(_), Some(_)) => NotEqual,
_ => Incompatible,
}
}
(Value::Number(n), Value::Bool(b)) | (Value::Bool(b), Value::Number(n)) => {
if n.as_f64() == Some(if *b { 1.0 } else { 0.0 }) {
Equal
} else {
NotEqual
}
}
(Value::String(s), Value::Bool(b)) | (Value::Bool(b), Value::String(s)) => {
if s == if *b { "true" } else { "false" } {
Equal
} else {
NotEqual
}
}
(Value::Null, Value::Number(n)) | (Value::Number(n), Value::Null) => {
if n.as_f64() == Some(0.0) {
Equal
} else {
NotEqual
}
}
(Value::Null, Value::Bool(b)) | (Value::Bool(b), Value::Null) => {
if !*b {
Equal
} else {
NotEqual
}
}
(Value::Null, Value::String(s)) | (Value::String(s), Value::Null) => {
if s.is_empty() {
Equal
} else {
NotEqual
}
}
(Value::Array(_), _) | (_, Value::Array(_))
if !matches!((left, right), (Value::Array(_), Value::Array(_))) =>
{
Incompatible
}
(Value::Object(_), _) | (_, Value::Object(_))
if !matches!((left, right), (Value::Object(_), Value::Object(_))) =>
{
Incompatible
}
(Value::Array(a), Value::Array(b)) => {
if a.len() == b.len() && a.iter().zip(b.iter()).all(|(av, bv)| av == bv) {
Equal
} else {
Incompatible
}
}
_ => NotEqual,
}
}
pub fn loose_equals_with_error(left: &Value, right: &Value) -> crate::Result<bool> {
use crate::Error;
match loose_equals_core(left, right) {
LooseEqualsResult::Equal => Ok(true),
LooseEqualsResult::NotEqual => Ok(false),
LooseEqualsResult::Incompatible => Err(Error::InvalidArguments(NAN_ERROR.into())),
}
}
pub fn try_coerce_to_integer(value: &Value, engine: &crate::DataLogic) -> Option<i64> {
match value {
Value::Number(n) => n.as_i64(),
Value::String(s) => {
if s.is_empty() && engine.config().numeric_coercion.empty_string_to_zero {
Some(0)
} else {
s.parse().ok()
}
}
Value::Bool(b) if engine.config().numeric_coercion.bool_to_number => {
Some(if *b { 1 } else { 0 })
}
Value::Null if engine.config().numeric_coercion.null_to_zero => Some(0),
_ => None,
}
}
pub fn loose_equals(left: &Value, right: &Value, engine: &crate::DataLogic) -> crate::Result<bool> {
if engine.config().loose_equality_errors {
loose_equals_with_error(left, right)
} else {
match loose_equals_core(left, right) {
LooseEqualsResult::Equal => Ok(true),
LooseEqualsResult::NotEqual | LooseEqualsResult::Incompatible => Ok(false),
}
}
}