Skip to main content

ferrijs_std/json/
parse.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::utils::bytes::ObjectBytes;
5use rquickjs::{Array, Ctx, Exception, IntoJs, Null, Object, Result, Undefined, Value};
6use simd_json::{Node, StaticNode};
7
8pub fn json_parse_string<'js>(ctx: Ctx<'js>, bytes: ObjectBytes<'js>) -> Result<Value<'js>> {
9    let bytes = bytes.as_bytes(&ctx)?;
10    json_parse(&ctx, bytes)
11}
12
13pub fn json_parse<'js, T: Into<Vec<u8>>>(ctx: &Ctx<'js>, json: T) -> Result<Value<'js>> {
14    let mut json: Vec<u8> = json.into();
15    let tape = match simd_json::to_tape(&mut json) {
16        Ok(tape) => tape,
17        Err(err) => {
18            // simd_json is strict about lone / unpaired surrogate escapes
19            // (`\uXXXX` where XXXX is a surrogate code point). Fall back to
20            // QuickJS's native `JSON.parse`, which is more permissive and is
21            // needed for spec compliance with content that round-trips
22            // through `JSON.stringify` of strings containing lone surrogates.
23            if err.character() == Some('u') {
24                if let Ok(value) = ctx.json_parse(json.as_slice()) {
25                    return Ok(value);
26                }
27            }
28            let mut itoa = itoa::Buffer::new();
29            let mut error_msg = String::with_capacity(256);
30            let json_length = json.len();
31            if json_length < 128 {
32                error_msg.reserve(json_length);
33                error_msg.push('\"');
34                error_msg.push_str(&std::string::String::from_utf8_lossy(&json));
35                error_msg.push_str("\" ");
36            }
37
38            error_msg.push_str("not valid JSON at index ");
39            error_msg.push_str(itoa.format(err.index()));
40            if let Some(char) = err.character() {
41                error_msg.push_str(" ('");
42                error_msg.push(char);
43                error_msg.push_str("')");
44            }
45            return Err(Exception::throw_syntax(ctx, &error_msg));
46        },
47    };
48    let tape = tape.0;
49
50    if let Some(first) = tape.first() {
51        return match first {
52            Node::String(value) => value.into_js(ctx),
53            Node::Static(node) => static_node_to_value(ctx, *node),
54            _ => parse_node(ctx, &tape, 0).map(|(value, _)| value),
55        };
56    }
57
58    Undefined.into_js(ctx)
59}
60
61#[inline(always)]
62fn static_node_to_value<'js>(ctx: &Ctx<'js>, node: StaticNode) -> Result<Value<'js>> {
63    match node {
64        StaticNode::I64(value) => value.into_js(ctx),
65        StaticNode::U64(value) => value.into_js(ctx),
66        StaticNode::F64(value) => value.into_js(ctx),
67        StaticNode::Bool(value) => value.into_js(ctx),
68        StaticNode::Null => Null.into_js(ctx),
69    }
70}
71
72fn parse_node<'js>(ctx: &Ctx<'js>, tape: &[Node], index: usize) -> Result<(Value<'js>, usize)> {
73    match tape[index] {
74        Node::String(value) => Ok((value.into_js(ctx)?, index + 1)),
75        Node::Static(node) => Ok((static_node_to_value(ctx, node)?, index + 1)),
76        Node::Object { len, .. } => {
77            let js_object = Object::new(ctx.clone())?;
78            let mut current_index = index + 1;
79
80            for _ in 0..len {
81                if let Node::String(key) = tape[current_index] {
82                    current_index += 1;
83                    let (value, new_index) = parse_node(ctx, tape, current_index)?;
84                    current_index = new_index;
85                    js_object.set(key, value)?;
86                }
87            }
88
89            Ok((js_object.into_value(), current_index))
90        },
91        Node::Array { len, .. } => {
92            let js_array = Array::new(ctx.clone())?;
93            let mut current_index = index + 1;
94
95            for i in 0..len {
96                let (value, new_index) = parse_node(ctx, tape, current_index)?;
97                current_index = new_index;
98                js_array.set(i, value)?;
99            }
100
101            Ok((js_array.into_value(), current_index))
102        },
103    }
104}