use super::json::{Object, Value};
use super::{format_number, number_of_text};
impl Value {
pub fn get(&self, key: &str) -> Option<&Value> {
match self {
Value::Object(o) => o.get(key),
_ => None,
}
}
pub fn at(&self, key: &str) -> &Value {
static NULL: Value = Value::Null;
self.get(key).unwrap_or(&NULL)
}
pub fn is_object(&self) -> bool {
matches!(self, Value::Object(_))
}
pub fn is_array(&self) -> bool {
matches!(self, Value::Array(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
pub fn as_object_mut(&mut self) -> Option<&mut Object> {
match self {
Value::Object(o) => Some(o),
_ => None,
}
}
pub fn as_array_mut(&mut self) -> Option<&mut Vec<Value>> {
match self {
Value::Array(a) => Some(a),
_ => None,
}
}
pub fn finite(&self) -> Option<f64> {
match self {
Value::Number(n) if n.is_finite() => Some(*n),
_ => None,
}
}
}
pub fn js_string(value: &Value) -> String {
match value {
Value::Null => "null".into(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => format_number(*n),
Value::String(s) => s.clone(),
Value::Array(a) => {
a.iter().map(|v| if v.is_null() { String::new() } else { js_string(v) }).collect::<Vec<_>>().join(",")
}
Value::Object(_) => "[object Object]".into(),
}
}
pub fn str_or_empty(value: Option<&Value>) -> String {
match value {
None | Some(Value::Null) => String::new(),
Some(v) => js_string(v),
}
}
pub fn js_number(value: &Value) -> f64 {
match value {
Value::Null => 0.0,
Value::Bool(b) => f64::from(u8::from(*b)),
Value::Number(n) => *n,
Value::String(s) => text_number(s),
Value::Array(_) => text_number(&js_string(value)),
Value::Object(_) => f64::NAN,
}
}
pub fn text_number(s: &str) -> f64 {
if super::trim(s).is_empty() { 0.0 } else { number_of_text(s) }
}
pub fn opt_number(value: Option<&Value>) -> f64 {
value.map_or(f64::NAN, js_number)
}
pub fn truthy(value: &Value) -> bool {
match value {
Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => *n != 0.0 && !n.is_nan(),
Value::String(s) => !s.is_empty(),
Value::Array(_) | Value::Object(_) => true,
}
}
pub fn opt_truthy(value: Option<&Value>) -> bool {
value.is_some_and(truthy)
}
pub fn round(x: f64) -> f64 {
if !x.is_finite() {
return x;
}
let f = x.floor();
if x - f >= 0.5 { f + 1.0 } else { f }
}
pub fn round_i64(x: f64) -> i64 {
super::to_i64(round(x))
}
pub fn parse_int(text: &str) -> f64 {
let t = super::trim_start(text);
let (sign, rest) = match t.as_bytes().first() {
Some(b'-') => (-1.0, &t[1..]),
Some(b'+') => (1.0, &t[1..]),
_ => (1.0, t),
};
let digits: &str = &rest[..rest.bytes().take_while(u8::is_ascii_digit).count()];
if digits.is_empty() {
return f64::NAN;
}
sign * digits.parse::<f64>().unwrap_or(f64::NAN)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::js::parse;
#[test]
fn values_read_as_javascript_reads_them() {
let v = parse(r#"{"a":[1,null,"x"],"b":{},"c":" 12 ","d":"","e":true}"#).unwrap();
assert_eq!(js_string(v.at("a")), "1,,x");
assert_eq!(js_string(v.at("b")), "[object Object]");
assert_eq!(js_string(v.at("zz")), "null");
assert_eq!(str_or_empty(v.get("zz")), "");
assert_eq!(js_number(v.at("c")), 12.0);
assert_eq!(js_number(v.at("d")), 0.0);
assert_eq!(js_number(v.at("e")), 1.0);
assert!(js_number(v.at("b")).is_nan());
assert!(opt_number(v.get("zz")).is_nan());
assert!(truthy(v.at("b")));
assert!(!truthy(v.at("d")));
assert_eq!(round(-2.5), -2.0);
assert_eq!(round(2.5), 3.0);
assert_eq!(round(0.49999999999999994), 0.0);
assert_eq!(parse_int(" 42px"), 42.0);
assert!(parse_int("px").is_nan());
}
}