Skip to main content

browser_commander/traces/
jsonfmt.rs

1//! JSON that keeps the key order JavaScript would keep.
2//!
3//! A trace is compared byte for byte across JavaScript, Python and Rust, and a
4//! JavaScript object serializes its keys in insertion order (array-index keys
5//! first, ascending). `serde_json::Value` sorts keys unless the crate-wide
6//! `preserve_order` feature is on, and switching that on would change every
7//! other `serde_json` user in the dependency tree. This module is the small
8//! ordered value the recorder writes instead, with `JSON.stringify` output.
9
10use std::fmt;
11
12use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
13
14/// A JSON value whose objects keep JavaScript's key order.
15#[derive(Debug, Clone, PartialEq, Default)]
16pub enum Json {
17    /// `null`.
18    #[default]
19    Null,
20    /// `true` or `false`.
21    Bool(bool),
22    /// Every number is a double, as in JavaScript.
23    Number(f64),
24    /// A string.
25    String(String),
26    /// An array.
27    Array(Vec<Json>),
28    /// An object, in JavaScript property order.
29    Object(JsonObject),
30}
31
32/// An object whose keys iterate the way a JavaScript object's do.
33#[derive(Debug, Clone, PartialEq, Default)]
34pub struct JsonObject {
35    entries: Vec<(String, Json)>,
36}
37
38/// Whether JavaScript treats `key` as an array index, which it orders first.
39fn is_array_index(key: &str) -> bool {
40    if key.is_empty() || key.len() > 10 || !key.bytes().all(|b| b.is_ascii_digit()) {
41        return false;
42    }
43    if key.len() > 1 && key.starts_with('0') {
44        return false;
45    }
46    key.parse::<u64>()
47        .map(|n| n < u64::from(u32::MAX))
48        .unwrap_or(false)
49}
50
51impl JsonObject {
52    /// An empty object.
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    /// Set `key`, keeping its position when it already exists.
58    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<Json>) {
59        let key = key.into();
60        let value = value.into();
61        if let Some(slot) = self.entries.iter_mut().find(|(name, _)| *name == key) {
62            slot.1 = value;
63            return;
64        }
65        if is_array_index(&key) {
66            let number: u64 = key.parse().unwrap_or(0);
67            let at = self
68                .entries
69                .iter()
70                .position(|(name, _)| {
71                    !is_array_index(name) || name.parse::<u64>().unwrap_or(0) > number
72                })
73                .unwrap_or(self.entries.len());
74            self.entries.insert(at, (key, value));
75        } else {
76            self.entries.push((key, value));
77        }
78    }
79
80    /// Builder form of [`JsonObject::insert`].
81    pub fn with(mut self, key: impl Into<String>, value: impl Into<Json>) -> Self {
82        self.insert(key, value);
83        self
84    }
85
86    /// Copy every entry of `other` over this object, like `{ ...this, ...other }`.
87    pub fn extend_from(&mut self, other: &JsonObject) {
88        for (key, value) in &other.entries {
89            self.insert(key.clone(), value.clone());
90        }
91    }
92
93    /// The value at `key`, if present.
94    pub fn get(&self, key: &str) -> Option<&Json> {
95        self.entries
96            .iter()
97            .find(|(name, _)| name == key)
98            .map(|(_, value)| value)
99    }
100
101    /// Remove `key`, returning its value.
102    pub fn remove(&mut self, key: &str) -> Option<Json> {
103        let at = self.entries.iter().position(|(name, _)| name == key)?;
104        Some(self.entries.remove(at).1)
105    }
106
107    /// Whether `key` is present.
108    pub fn contains_key(&self, key: &str) -> bool {
109        self.get(key).is_some()
110    }
111
112    /// Entries in JavaScript order.
113    pub fn iter(&self) -> impl Iterator<Item = (&String, &Json)> {
114        self.entries.iter().map(|(key, value)| (key, value))
115    }
116
117    /// Number of entries.
118    pub fn len(&self) -> usize {
119        self.entries.len()
120    }
121
122    /// Whether the object has no entries.
123    pub fn is_empty(&self) -> bool {
124        self.entries.is_empty()
125    }
126}
127
128impl Json {
129    /// Parse JSON text the way `JSON.parse` does: a repeated key keeps its
130    /// first position and its last value.
131    pub fn parse(text: &str) -> Result<Json, serde_json::Error> {
132        serde_json::from_str(text)
133    }
134
135    /// `JSON.stringify(value)`.
136    pub fn to_compact(&self) -> String {
137        let mut out = String::new();
138        write_value(&mut out, self, None, 0);
139        out
140    }
141
142    /// `JSON.stringify(value, null, 2)`.
143    pub fn to_pretty(&self) -> String {
144        let mut out = String::new();
145        write_value(&mut out, self, Some(2), 0);
146        out
147    }
148
149    /// The object, when this is one.
150    pub fn as_object(&self) -> Option<&JsonObject> {
151        match self {
152            Json::Object(object) => Some(object),
153            _ => None,
154        }
155    }
156
157    /// The array, when this is one.
158    pub fn as_array(&self) -> Option<&Vec<Json>> {
159        match self {
160            Json::Array(items) => Some(items),
161            _ => None,
162        }
163    }
164
165    /// The string, when this is one.
166    pub fn as_str(&self) -> Option<&str> {
167        match self {
168            Json::String(text) => Some(text),
169            _ => None,
170        }
171    }
172
173    /// The number, when this is one.
174    pub fn as_f64(&self) -> Option<f64> {
175        match self {
176            Json::Number(number) => Some(*number),
177            _ => None,
178        }
179    }
180
181    /// The value at `key` of an object.
182    pub fn get(&self, key: &str) -> Option<&Json> {
183        self.as_object().and_then(|object| object.get(key))
184    }
185
186    /// Whether this is `null`.
187    pub fn is_null(&self) -> bool {
188        matches!(self, Json::Null)
189    }
190
191    /// JavaScript truthiness.
192    pub fn truthy(&self) -> bool {
193        match self {
194            Json::Null => false,
195            Json::Bool(value) => *value,
196            Json::Number(number) => *number != 0.0 && !number.is_nan(),
197            Json::String(text) => !text.is_empty(),
198            Json::Array(_) | Json::Object(_) => true,
199        }
200    }
201
202    /// Convert to a `serde_json::Value`; objects come out key-sorted unless
203    /// the caller enabled `preserve_order`.
204    pub fn to_value(&self) -> serde_json::Value {
205        match self {
206            Json::Null => serde_json::Value::Null,
207            Json::Bool(value) => serde_json::Value::Bool(*value),
208            Json::Number(number) => {
209                if number.fract() == 0.0 && number.abs() < 9_007_199_254_740_992.0 {
210                    serde_json::Value::from(*number as i64)
211                } else {
212                    serde_json::Number::from_f64(*number)
213                        .map(serde_json::Value::Number)
214                        .unwrap_or(serde_json::Value::Null)
215                }
216            }
217            Json::String(text) => serde_json::Value::String(text.clone()),
218            Json::Array(items) => {
219                serde_json::Value::Array(items.iter().map(Json::to_value).collect())
220            }
221            Json::Object(object) => serde_json::Value::Object(
222                object
223                    .iter()
224                    .map(|(key, value)| (key.clone(), value.to_value()))
225                    .collect(),
226            ),
227        }
228    }
229}
230
231/// JavaScript's `String(value)` for a value that may be missing (`undefined`).
232pub fn js_string(value: Option<&Json>) -> String {
233    match value {
234        None => "undefined".to_string(),
235        Some(Json::Null) => "null".to_string(),
236        Some(Json::Bool(value)) => value.to_string(),
237        Some(Json::Number(number)) => js_number(*number),
238        Some(Json::String(text)) => text.clone(),
239        Some(Json::Array(items)) => items
240            .iter()
241            .map(|item| match item {
242                Json::Null => String::new(),
243                other => js_string(Some(other)),
244            })
245            .collect::<Vec<_>>()
246            .join(","),
247        Some(Json::Object(_)) => "[object Object]".to_string(),
248    }
249}
250
251/// `left !== right` negated, for values that may be missing (`undefined`).
252/// Objects and arrays read from JSON are never the same reference.
253pub fn js_strict_equal(left: Option<&Json>, right: Option<&Json>) -> bool {
254    match (left, right) {
255        (None, None) => true,
256        (Some(Json::Null), Some(Json::Null)) => true,
257        (Some(Json::Bool(a)), Some(Json::Bool(b))) => a == b,
258        (Some(Json::Number(a)), Some(Json::Number(b))) => a == b,
259        (Some(Json::String(a)), Some(Json::String(b))) => a == b,
260        _ => false,
261    }
262}
263
264/// Length in UTF-16 code units, which is what JavaScript's `length` counts.
265pub fn utf16_len(text: &str) -> usize {
266    text.encode_utf16().count()
267}
268
269/// `Number.prototype.toString()` for a double.
270pub fn js_number(number: f64) -> String {
271    if number.is_nan() {
272        return "NaN".to_string();
273    }
274    if number.is_infinite() {
275        return if number > 0.0 {
276            "Infinity"
277        } else {
278            "-Infinity"
279        }
280        .to_string();
281    }
282    if number == 0.0 {
283        return "0".to_string();
284    }
285    let sign = if number < 0.0 { "-" } else { "" };
286    // `{:e}` prints the shortest digits that round-trip, as ECMAScript asks.
287    let formatted = format!("{:e}", number.abs());
288    let (mantissa, exponent) = formatted.split_once('e').unwrap_or((&formatted, "0"));
289    let digits: String = mantissa.chars().filter(|c| *c != '.').collect();
290    let exponent: i64 = exponent.parse().unwrap_or(0);
291    let k = digits.len() as i64;
292    let n = exponent + 1;
293
294    let body = if k <= n && n <= 21 {
295        format!("{digits}{}", "0".repeat((n - k) as usize))
296    } else if 0 < n && n <= 21 {
297        let (whole, fraction) = digits.split_at(n as usize);
298        format!("{whole}.{fraction}")
299    } else if -6 < n && n <= 0 {
300        format!("0.{}{digits}", "0".repeat((-n) as usize))
301    } else {
302        let e = n - 1;
303        let e_sign = if e < 0 { '-' } else { '+' };
304        if k == 1 {
305            format!("{digits}e{e_sign}{}", e.abs())
306        } else {
307            let (first, rest) = digits.split_at(1);
308            format!("{first}.{rest}e{e_sign}{}", e.abs())
309        }
310    };
311    format!("{sign}{body}")
312}
313
314fn write_string(out: &mut String, text: &str) {
315    out.push('"');
316    for c in text.chars() {
317        match c {
318            '"' => out.push_str("\\\""),
319            '\\' => out.push_str("\\\\"),
320            '\u{8}' => out.push_str("\\b"),
321            '\u{c}' => out.push_str("\\f"),
322            '\n' => out.push_str("\\n"),
323            '\r' => out.push_str("\\r"),
324            '\t' => out.push_str("\\t"),
325            c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
326            c => out.push(c),
327        }
328    }
329    out.push('"');
330}
331
332fn newline(out: &mut String, indent: Option<usize>, depth: usize) {
333    if let Some(width) = indent {
334        out.push('\n');
335        out.push_str(&" ".repeat(width * depth));
336    }
337}
338
339fn write_value(out: &mut String, value: &Json, indent: Option<usize>, depth: usize) {
340    match value {
341        Json::Null => out.push_str("null"),
342        Json::Bool(value) => out.push_str(if *value { "true" } else { "false" }),
343        Json::Number(number) => {
344            if number.is_finite() {
345                out.push_str(&js_number(*number));
346            } else {
347                out.push_str("null");
348            }
349        }
350        Json::String(text) => write_string(out, text),
351        Json::Array(items) => {
352            if items.is_empty() {
353                out.push_str("[]");
354                return;
355            }
356            out.push('[');
357            for (at, item) in items.iter().enumerate() {
358                if at > 0 {
359                    out.push(',');
360                }
361                newline(out, indent, depth + 1);
362                write_value(out, item, indent, depth + 1);
363            }
364            newline(out, indent, depth);
365            out.push(']');
366        }
367        Json::Object(object) => {
368            if object.is_empty() {
369                out.push_str("{}");
370                return;
371            }
372            out.push('{');
373            for (at, (key, item)) in object.iter().enumerate() {
374                if at > 0 {
375                    out.push(',');
376                }
377                newline(out, indent, depth + 1);
378                write_string(out, key);
379                out.push(':');
380                if indent.is_some() {
381                    out.push(' ');
382                }
383                write_value(out, item, indent, depth + 1);
384            }
385            newline(out, indent, depth);
386            out.push('}');
387        }
388    }
389}
390
391impl fmt::Display for Json {
392    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393        f.write_str(&self.to_compact())
394    }
395}
396
397struct JsonVisitor;
398
399impl<'de> Visitor<'de> for JsonVisitor {
400    type Value = Json;
401
402    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        f.write_str("any JSON value")
404    }
405
406    fn visit_bool<E>(self, value: bool) -> Result<Json, E> {
407        Ok(Json::Bool(value))
408    }
409
410    fn visit_i64<E>(self, value: i64) -> Result<Json, E> {
411        Ok(Json::Number(value as f64))
412    }
413
414    fn visit_u64<E>(self, value: u64) -> Result<Json, E> {
415        Ok(Json::Number(value as f64))
416    }
417
418    fn visit_f64<E>(self, value: f64) -> Result<Json, E> {
419        Ok(Json::Number(value))
420    }
421
422    fn visit_str<E>(self, value: &str) -> Result<Json, E> {
423        Ok(Json::String(value.to_string()))
424    }
425
426    fn visit_string<E>(self, value: String) -> Result<Json, E> {
427        Ok(Json::String(value))
428    }
429
430    fn visit_unit<E>(self) -> Result<Json, E> {
431        Ok(Json::Null)
432    }
433
434    fn visit_none<E>(self) -> Result<Json, E> {
435        Ok(Json::Null)
436    }
437
438    fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Json, D::Error> {
439        Deserialize::deserialize(deserializer)
440    }
441
442    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Json, A::Error> {
443        let mut items = Vec::new();
444        while let Some(item) = seq.next_element()? {
445            items.push(item);
446        }
447        Ok(Json::Array(items))
448    }
449
450    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Json, A::Error> {
451        let mut object = JsonObject::new();
452        while let Some((key, value)) = map.next_entry::<String, Json>()? {
453            object.insert(key, value);
454        }
455        Ok(Json::Object(object))
456    }
457}
458
459impl<'de> Deserialize<'de> for Json {
460    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Json, D::Error> {
461        deserializer.deserialize_any(JsonVisitor)
462    }
463}
464
465impl From<&serde_json::Value> for Json {
466    fn from(value: &serde_json::Value) -> Self {
467        match value {
468            serde_json::Value::Null => Json::Null,
469            serde_json::Value::Bool(value) => Json::Bool(*value),
470            serde_json::Value::Number(number) => Json::Number(number.as_f64().unwrap_or(f64::NAN)),
471            serde_json::Value::String(text) => Json::String(text.clone()),
472            serde_json::Value::Array(items) => Json::Array(items.iter().map(Json::from).collect()),
473            serde_json::Value::Object(map) => {
474                let mut object = JsonObject::new();
475                for (key, item) in map {
476                    object.insert(key.clone(), Json::from(item));
477                }
478                Json::Object(object)
479            }
480        }
481    }
482}
483
484impl From<serde_json::Value> for Json {
485    fn from(value: serde_json::Value) -> Self {
486        Json::from(&value)
487    }
488}
489
490impl From<&str> for Json {
491    fn from(value: &str) -> Self {
492        Json::String(value.to_string())
493    }
494}
495
496impl From<String> for Json {
497    fn from(value: String) -> Self {
498        Json::String(value)
499    }
500}
501
502impl From<&String> for Json {
503    fn from(value: &String) -> Self {
504        Json::String(value.clone())
505    }
506}
507
508impl From<bool> for Json {
509    fn from(value: bool) -> Self {
510        Json::Bool(value)
511    }
512}
513
514impl From<f64> for Json {
515    fn from(value: f64) -> Self {
516        Json::Number(value)
517    }
518}
519
520macro_rules! json_from_integer {
521    ($($kind:ty),*) => {
522        $(impl From<$kind> for Json {
523            fn from(value: $kind) -> Self {
524                Json::Number(value as f64)
525            }
526        })*
527    };
528}
529
530json_from_integer!(i32, i64, u32, u64, usize);
531
532impl From<JsonObject> for Json {
533    fn from(value: JsonObject) -> Self {
534        Json::Object(value)
535    }
536}
537
538impl From<Vec<Json>> for Json {
539    fn from(value: Vec<Json>) -> Self {
540        Json::Array(value)
541    }
542}
543
544impl<T: Into<Json>> From<Option<T>> for Json {
545    fn from(value: Option<T>) -> Self {
546        value.map(Into::into).unwrap_or(Json::Null)
547    }
548}
549
550impl FromIterator<(String, Json)> for JsonObject {
551    fn from_iter<I: IntoIterator<Item = (String, Json)>>(iter: I) -> Self {
552        let mut object = JsonObject::new();
553        for (key, value) in iter {
554            object.insert(key, value);
555        }
556        object
557    }
558}
559
560#[cfg(test)]
561mod tests {
562    use super::*;
563
564    #[test]
565    fn numbers_print_like_javascript() {
566        let cases = [
567            (0.0, "0"),
568            (-0.0, "0"),
569            (1.0, "1"),
570            (2.0, "2"),
571            (0.25, "0.25"),
572            (1234.5, "1234.5"),
573            (1e21, "1e+21"),
574            (1.5e21, "1.5e+21"),
575            (123e18, "123000000000000000000"),
576            (1e-7, "1e-7"),
577            (1.25e-7, "1.25e-7"),
578            (0.000001, "0.000001"),
579            (0.1 + 0.2, "0.30000000000000004"),
580            (-42.0, "-42"),
581            (1767225600000.0, "1767225600000"),
582        ];
583        for (number, expected) in cases {
584            assert_eq!(js_number(number), expected, "{number}");
585        }
586    }
587
588    #[test]
589    fn objects_keep_javascript_key_order() {
590        let parsed = Json::parse(r#"{"b":1,"a":2,"10":3,"2":4,"b":5,"01":6}"#).unwrap();
591        assert_eq!(parsed.to_compact(), r#"{"2":4,"10":3,"b":5,"a":2,"01":6}"#);
592    }
593
594    #[test]
595    fn strings_escape_like_json_stringify() {
596        let value = Json::from("a\"b\\c\n\u{1}\u{2028}✓");
597        assert_eq!(value.to_compact(), "\"a\\\"b\\\\c\\n\\u0001\u{2028}✓\"");
598    }
599
600    #[test]
601    fn pretty_output_matches_json_stringify_with_two_spaces() {
602        let value = Json::parse(r#"{"a":[1,{"b":[]}],"c":{}}"#).unwrap();
603        assert_eq!(
604            value.to_pretty(),
605            "{\n  \"a\": [\n    1,\n    {\n      \"b\": []\n    }\n  ],\n  \"c\": {}\n}"
606        );
607    }
608}