nanvm_lib/serializer/
to_json.rs

1use crate::{
2    js::{
3        any::Any,
4        js_string::JsStringRef,
5        visitor::{to_visitor, Visitor},
6    },
7    mem::{
8        flexible_array::{header::FlexibleArrayHeader, FlexibleArray},
9        manager::Dealloc,
10        ref_::Ref,
11    },
12};
13
14use core::{
15    fmt::{self, Write},
16    result,
17};
18
19const ESCAPE_B: u8 = 0x08;
20const ESCAPE_F: u8 = 0x0C;
21
22pub trait WriteJson: Write {
23    fn write_u4_hex(&mut self, v: u16) -> fmt::Result {
24        self.write_char(b"0123456789ABCDEF"[v as usize & 0xF] as char)
25    }
26
27    fn write_js_escape(&mut self, c: u16) -> fmt::Result {
28        self.write_str("\\u")?;
29        self.write_u4_hex(c >> 12)?;
30        self.write_u4_hex(c >> 8)?;
31        self.write_u4_hex(c >> 4)?;
32        self.write_u4_hex(c)
33    }
34
35    /// See https://www.json.org/json-en.html
36    fn write_js_string(&mut self, s: &JsStringRef<impl Dealloc>) -> fmt::Result {
37        self.write_char('"')?;
38        for &c in s.items().iter() {
39            if c < 0x80 {
40                match c as u8 {
41                    b'\\' => self.write_str(r#"\\"#)?,
42                    b'"' => self.write_str(r#"\""#)?,
43                    ESCAPE_B => self.write_str(r#"\b"#)?,
44                    ESCAPE_F => self.write_str(r#"\f"#)?,
45                    b'\n' => self.write_str(r#"\n"#)?,
46                    b'\r' => self.write_str(r#"\r"#)?,
47                    b'\t' => self.write_str(r#"\t"#)?,
48                    c if c < 0x20 => self.write_js_escape(c as u16)?,
49                    c => self.write_char(c as char)?,
50                }
51            } else {
52                self.write_js_escape(c)?;
53            }
54        }
55        self.write_char('"')
56    }
57
58    fn write_list<I>(
59        &mut self,
60        open: char,
61        close: char,
62        v: Ref<FlexibleArray<I, impl FlexibleArrayHeader>, impl Dealloc>,
63        f: impl Fn(&mut Self, &I) -> fmt::Result,
64    ) -> fmt::Result {
65        let mut comma = "";
66        self.write_char(open)?;
67        for i in v.items().iter() {
68            self.write_str(comma)?;
69            f(self, i)?;
70            comma = ",";
71        }
72        self.write_char(close)
73    }
74
75    fn write_json(&mut self, any: Any<impl Dealloc>) -> fmt::Result {
76        match to_visitor(any) {
77            // TODO: replace with proper JSON number serializer.
78            Visitor::Number(n) => self.write_str(n.to_string().as_str()),
79            Visitor::Null => self.write_str("null"),
80            Visitor::Bool(b) => self.write_str(if b { "true" } else { "false" }),
81            Visitor::String(s) => self.write_js_string(&s),
82            Visitor::Object(o) => self.write_list('{', '}', o, |w, (k, v)| {
83                w.write_js_string(k)?;
84                w.write_char(':')?;
85                w.write_json(v.clone())
86            }),
87            Visitor::Array(a) => self.write_list('[', ']', a, |w, i| w.write_json(i.clone())),
88            Visitor::Bigint(_) => todo!(),
89        }
90    }
91}
92
93impl<T: Write> WriteJson for T {}
94
95pub fn to_json(a: Any<impl Dealloc>) -> result::Result<String, fmt::Error> {
96    let mut s = String::default();
97    s.write_json(a)?;
98    Ok(s)
99}
100
101#[cfg(test)]
102mod test {
103    use wasm_bindgen_test::wasm_bindgen_test;
104
105    use crate::{
106        js::{any::Any, any_cast::AnyCast, js_string::new_string, new::New, null::Null},
107        mem::global::{Global, GLOBAL},
108        serializer::to_json::WriteJson,
109    };
110
111    #[test]
112    #[wasm_bindgen_test]
113    fn test() {
114        type A = Any<Global>;
115        let s = new_string(
116            GLOBAL,
117            ['a' as u16, '\\' as u16, 'b' as u16, '"' as u16, 31],
118        )
119        .to_ref();
120        let a = GLOBAL.new_js_array([
121            1.0.move_to_any(),
122            true.move_to_any(),
123            Null().move_to_any(),
124            GLOBAL.new_js_array([]),
125            GLOBAL.new_js_string([]),
126            GLOBAL.new_js_object([]),
127            GLOBAL.new_js_object([(s, 2.0.move_to_any())]),
128        ]);
129        let mut s = String::new();
130        s.write_json(a).unwrap();
131        assert_eq!(s, r#"[1,true,null,[],"",{},{"a\\b\"\u001F":2}]"#);
132    }
133}