1use serde_json::{Map, Number, Value};
6
7pub fn number(v: f64) -> Value {
10 if !v.is_finite() {
11 return Value::Null;
12 }
13 if v.fract() == 0.0 && v.abs() < 9_007_199_254_740_992.0 {
14 if v < 0.0 {
15 return Value::from(v as i64);
16 }
17 return Value::from(v as u64);
18 }
19 Number::from_f64(v).map(Value::Number).unwrap_or(Value::Null)
20}
21
22pub fn as_f64(v: &Value) -> Option<f64> {
24 v.as_f64()
25}
26
27pub fn canonical(v: &Value) -> Value {
30 match v {
31 Value::Number(n) => number(n.as_f64().unwrap_or(0.0)),
32 Value::Array(items) => Value::Array(items.iter().map(canonical).collect()),
33 Value::Object(map) => Value::Object(map.iter().map(|(k, v)| (k.clone(), canonical(v))).collect()),
34 other => other.clone(),
35 }
36}
37
38pub fn equal(a: &Value, b: &Value) -> bool {
40 canonical(a) == canonical(b)
41}
42
43pub fn number_to_string(v: f64) -> String {
47 if v.is_nan() {
48 return "NaN".into();
49 }
50 if v.is_infinite() {
51 return if v > 0.0 { "Infinity".into() } else { "-Infinity".into() };
52 }
53 if v == 0.0 {
54 return "0".into();
55 }
56 let sign = if v < 0.0 { "-" } else { "" };
57 let (digits, n) = crate::format::shortest_digits(v.abs());
58 let k = digits.len() as i32;
59 let body = if k <= n && n <= 21 {
60 format!("{digits}{}", "0".repeat((n - k) as usize))
61 } else if 0 < n && n <= 21 {
62 format!("{}.{}", &digits[..n as usize], &digits[n as usize..])
63 } else if -6 < n && n <= 0 {
64 format!("0.{}{digits}", "0".repeat((-n) as usize))
65 } else {
66 let e = n - 1;
67 let exp = if e >= 0 { format!("e+{e}") } else { format!("e{e}") };
68 if k == 1 {
69 format!("{digits}{exp}")
70 } else {
71 format!("{}.{}{exp}", &digits[..1], &digits[1..])
72 }
73 };
74 format!("{sign}{body}")
75}
76
77pub fn to_js_string(v: &Value) -> String {
82 match v {
83 Value::Null => "null".into(),
84 Value::Bool(b) => b.to_string(),
85 Value::Number(n) => number_to_string(n.as_f64().unwrap_or(0.0)),
86 Value::String(s) => s.clone(),
87 Value::Array(items) => items.iter().map(to_js_string).collect::<Vec<_>>().join(","),
88 Value::Object(_) => "[object Object]".into(),
89 }
90}
91
92pub fn to_number(v: &Value) -> f64 {
94 match v {
95 Value::Null => 0.0,
96 Value::Bool(b) => {
97 if *b {
98 1.0
99 } else {
100 0.0
101 }
102 }
103 Value::Number(n) => n.as_f64().unwrap_or(0.0),
104 Value::String(s) => {
105 let t = s.trim();
106 if t.is_empty() {
107 0.0
108 } else {
109 t.parse::<f64>().unwrap_or(0.0)
110 }
111 }
112 Value::Array(items) => {
113 if items.len() == 1 {
114 to_number(&items[0])
115 } else {
116 0.0
117 }
118 }
119 Value::Object(_) => 0.0,
120 }
121}
122
123pub fn strict_eq(a: &Value, b: &Value) -> bool {
125 match (a, b) {
126 (Value::Number(x), Value::Number(y)) => x.as_f64() == y.as_f64(),
127 (Value::Object(_), Value::Object(_)) | (Value::Array(_), Value::Array(_)) => false,
128 _ => a == b,
129 }
130}
131
132pub fn obj() -> Map<String, Value> {
134 Map::new()
135}
136
137pub fn as_obj(v: &Value) -> Option<&Map<String, Value>> {
139 v.as_object()
140}
141
142pub fn str_of<'a>(m: &'a Map<String, Value>, k: &str) -> Option<&'a str> {
144 m.get(k).and_then(Value::as_str)
145}
146
147pub fn clone_value<T: serde::Serialize + serde::de::DeserializeOwned>(v: &T) -> T {
149 serde_json::from_value(serde_json::to_value(v).expect("serialize")).expect("deserialize")
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use serde_json::json;
156
157 #[test]
158 fn numbers_print_like_javascript() {
159 assert_eq!(number(2.0), json!(2));
160 assert_eq!(number(-3.0), json!(-3));
161 assert_eq!(number(66.67), json!(66.67));
162 assert_eq!(number_to_string(100.0), "100");
163 assert_eq!(number_to_string(66.67), "66.67");
164 assert_eq!(number_to_string(1.7777778), "1.7777778");
165 assert_eq!(number_to_string(123456789012345680000.0), "123456789012345680000");
166 assert_eq!(number_to_string(1e21), "1e+21");
167 assert_eq!(number_to_string(1e-7), "1e-7");
168 assert_eq!(number_to_string(1.5e-7), "1.5e-7");
169 assert_eq!(number_to_string(1e-6), "0.000001");
170 assert_eq!(number_to_string(-0.0), "0");
171 assert!(equal(&json!({"a": 2.0}), &json!({"a": 2})));
172 assert!(!equal(&json!({"a": 2.5}), &json!({"a": 2})));
173 }
174
175 #[test]
176 fn js_coercions() {
177 assert_eq!(to_number(&json!("12")), 12.0);
178 assert_eq!(to_number(&json!("x")), 0.0);
179 assert_eq!(to_number(&json!(null)), 0.0);
180 assert!(strict_eq(&json!(1), &json!(1.0)));
181 assert!(!strict_eq(&json!("1"), &json!(1)));
182 }
183}