Skip to main content

pipa/builtins/
json.rs

1use crate::object::array_obj::JSArrayObject;
2use crate::object::object::JSObject;
3use crate::runtime::context::JSContext;
4use crate::value::JSValue;
5
6pub fn json_parse(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
7    if args.is_empty() || !args[0].is_string() {
8        return JSValue::undefined();
9    }
10
11    let atom = args[0].get_atom();
12    let s = ctx.get_atom_str(atom).to_string();
13
14    match super::json_parser::JsonParser::new(&s).parse_value(ctx) {
15        Ok(v) => v,
16        Err(_) => JSValue::undefined(),
17    }
18}
19
20pub fn json_stringify(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
21    if args.is_empty() {
22        return JSValue::undefined();
23    }
24
25    let value = &args[0];
26    let replacer = args.get(1);
27    let space = args.get(2);
28
29    let json_str = jsvalue_to_json_with_options(ctx, value, replacer, space, &mut Vec::new());
30
31    JSValue::new_string(ctx.intern(&json_str))
32}
33
34fn jsvalue_to_json_with_options(
35    ctx: &mut JSContext,
36    value: &JSValue,
37    replacer: Option<&JSValue>,
38    space: Option<&JSValue>,
39    seen: &mut Vec<usize>,
40) -> String {
41    let processed_value = if let Some(repl) = replacer {
42        if repl.is_function() {
43            let vm_ptr = ctx.get_register_vm_ptr();
44            if let Some(ptr) = vm_ptr {
45                let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
46                let args = vec![JSValue::new_string(ctx.intern("")), *value];
47                if let Ok(result) = vm.call_function(ctx, *repl, &args) {
48                    result
49                } else {
50                    *value
51                }
52            } else {
53                *value
54            }
55        } else {
56            *value
57        }
58    } else {
59        *value
60    };
61
62    let base_json = jsvalue_to_json_internal(ctx, &processed_value, replacer, seen);
63
64    if let Some(sp) = space {
65        let indent = get_indent_string_with_ctx(ctx, sp);
66        if !indent.is_empty() {
67            return format_json(&base_json, &indent);
68        }
69    }
70
71    base_json
72}
73
74fn get_indent_string_with_ctx(ctx: &mut JSContext, space: &JSValue) -> String {
75    if space.is_int() {
76        let n = space.get_int().min(10).max(0) as usize;
77        " ".repeat(n)
78    } else if space.is_string() {
79        let atom = space.get_atom();
80        let s_str = ctx.get_atom_str(atom).to_string();
81        s_str.chars().take(10).collect()
82    } else {
83        String::new()
84    }
85}
86
87fn format_json(json: &str, indent: &str) -> String {
88    let mut result = String::new();
89    let mut level = 0;
90    let mut in_string = false;
91    let mut escape = false;
92    let chars: Vec<char> = json.chars().collect();
93
94    for i in 0..chars.len() {
95        let c = chars[i];
96
97        if escape {
98            escape = false;
99            result.push(c);
100            continue;
101        }
102
103        if c == '\\' && in_string {
104            escape = true;
105            result.push(c);
106            continue;
107        }
108
109        if c == '"' {
110            in_string = !in_string;
111            result.push(c);
112            continue;
113        }
114
115        if in_string {
116            result.push(c);
117            continue;
118        }
119
120        match c {
121            '{' | '[' => {
122                result.push(c);
123                level += 1;
124                result.push('\n');
125                result.push_str(&indent.repeat(level));
126            }
127            '}' | ']' => {
128                level = level.saturating_sub(1);
129                result.push('\n');
130                result.push_str(&indent.repeat(level));
131                result.push(c);
132            }
133            ',' => {
134                result.push(c);
135                result.push('\n');
136                result.push_str(&indent.repeat(level));
137            }
138            ':' => {
139                result.push(c);
140                result.push(' ');
141            }
142            _ => {
143                result.push(c);
144            }
145        }
146    }
147
148    result
149}
150
151fn jsvalue_to_json_internal(
152    ctx: &mut JSContext,
153    value: &JSValue,
154    replacer: Option<&JSValue>,
155    seen: &mut Vec<usize>,
156) -> String {
157    if value.is_null() {
158        return "null".to_string();
159    }
160    if value.is_bool() {
161        return value.get_bool().to_string();
162    }
163    if value.is_int() {
164        return value.get_int().to_string();
165    }
166    if value.is_float() {
167        let f = value.get_float();
168        if f.is_nan() || f.is_infinite() {
169            return "null".to_string();
170        }
171        return f.to_string();
172    }
173    if value.is_string() {
174        let atom = value.get_atom();
175        let s = ctx.get_atom_str(atom);
176
177        let escaped = s
178            .replace('\\', "\\\\")
179            .replace('"', "\\\"")
180            .replace('\n', "\\n")
181            .replace('\r', "\\r")
182            .replace('\t', "\\t");
183        return format!("\"{}\"", escaped);
184    }
185    if value.is_undefined() || value.is_function() || value.is_symbol() {
186        return "null".to_string();
187    }
188
189    if value.is_object() {
190        let ptr = value.get_ptr() as usize;
191
192        if seen.contains(&ptr) {
193            return "null".to_string();
194        }
195        seen.push(ptr);
196
197        let obj = value.as_object();
198
199        if obj.is_array() {
200            let result = array_to_json(ctx, obj, replacer, seen);
201            seen.pop();
202            return result;
203        } else {
204            let result = object_to_json(ctx, obj, replacer, seen);
205            seen.pop();
206            return result;
207        }
208    }
209
210    "null".to_string()
211}
212
213fn array_to_json(
214    ctx: &mut JSContext,
215    arr: &JSObject,
216    replacer: Option<&JSValue>,
217    seen: &mut Vec<usize>,
218) -> String {
219    let len_atom = ctx.common_atoms.length;
220    let len = arr.get(len_atom).map(|v| v.get_int() as usize).unwrap_or(0);
221
222    let mut elements = Vec::new();
223    let arr_ptr = arr as *const JSObject as usize;
224    let is_jsarray = arr.is_dense_array();
225    for i in 0..len {
226        let val = if is_jsarray {
227            unsafe { &*(arr_ptr as *const JSArrayObject) }.get(i)
228        } else if let Some(v) = arr.get_indexed(i) {
229            Some(v)
230        } else {
231            let key = ctx.int_atom_mut(i);
232            arr.get(key)
233        };
234        if let Some(val) = val {
235            let processed = if let Some(repl) = replacer {
236                if repl.is_function() {
237                    let vm_ptr = ctx.get_register_vm_ptr();
238                    if let Some(ptr) = vm_ptr {
239                        let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
240                        let args = vec![JSValue::new_string(ctx.intern(&i.to_string())), val];
241                        if let Ok(result) = vm.call_function(ctx, *repl, &args) {
242                            result
243                        } else {
244                            val
245                        }
246                    } else {
247                        val
248                    }
249                } else {
250                    val
251                }
252            } else {
253                val
254            };
255            elements.push(jsvalue_to_json_internal(ctx, &processed, replacer, seen));
256        } else {
257            elements.push("null".to_string());
258        }
259    }
260
261    format!("[{}]", elements.join(","))
262}
263
264fn object_to_json(
265    ctx: &mut JSContext,
266    obj: &JSObject,
267    replacer: Option<&JSValue>,
268    seen: &mut Vec<usize>,
269) -> String {
270    let mut pairs = Vec::new();
271
272    let filter_keys: Option<Vec<crate::runtime::atom::Atom>> = if let Some(repl) = replacer {
273        if repl.is_object() {
274            let repl_obj = repl.as_object();
275            if repl_obj.is_array() {
276                let len_atom = ctx.common_atoms.length;
277                let len = repl_obj
278                    .get(len_atom)
279                    .map(|v| v.get_int() as usize)
280                    .unwrap_or(0);
281                let mut keys = Vec::new();
282                let repl_ptr = repl_obj as *const JSObject as usize;
283                let is_jsarray = repl_obj.is_dense_array();
284                for i in 0..len {
285                    let val = if is_jsarray {
286                        unsafe { &*(repl_ptr as *const JSArrayObject) }.get(i)
287                    } else if let Some(v) = repl_obj.get_indexed(i) {
288                        Some(v)
289                    } else {
290                        let key = ctx.int_atom_mut(i);
291                        repl_obj.get(key)
292                    };
293                    if let Some(k) = val {
294                        if k.is_string() {
295                            keys.push(k.get_atom());
296                        }
297                    }
298                }
299                Some(keys)
300            } else {
301                None
302            }
303        } else {
304            None
305        }
306    } else {
307        None
308    };
309
310    let properties: Vec<(crate::runtime::atom::Atom, JSValue)> = obj.own_properties();
311
312    for (key, val) in properties {
313        if let Some(ref filter) = filter_keys {
314            if !filter.contains(&key) {
315                continue;
316            }
317        }
318
319        let key_str = ctx.get_atom_str(key).to_string();
320
321        let processed = if let Some(repl) = replacer {
322            if repl.is_function() {
323                let vm_ptr = ctx.get_register_vm_ptr();
324                if let Some(ptr) = vm_ptr {
325                    let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
326                    let args = vec![JSValue::new_string(ctx.intern(&key_str)), val];
327                    if let Ok(result) = vm.call_function(ctx, *repl, &args) {
328                        result
329                    } else {
330                        val
331                    }
332                } else {
333                    val
334                }
335            } else {
336                val
337            }
338        } else {
339            val
340        };
341
342        if processed.is_undefined() || processed.is_function() || processed.is_symbol() {
343            continue;
344        }
345
346        let value_str = jsvalue_to_json_internal(ctx, &processed, replacer, seen);
347        let escaped_key = key_str.replace('\\', "\\\\").replace('"', "\\\"");
348        pairs.push(format!("\"{}\":{}", escaped_key, value_str));
349    }
350
351    format!("{{{}}}", pairs.join(","))
352}